From 199024ce7933dac137904f898af8a89919e8988c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Jul 2026 10:46:07 +0530 Subject: [PATCH 01/12] chore: update Android SDK to 25.3.0 --- CHANGELOG.md | 9 ++ README.md | 4 +- build.gradle.kts | 4 +- example/build.gradle.kts | 36 ++++---- gradle/wrapper/gradle-wrapper.properties | 2 +- library/build.gradle.kts | 32 +++---- library/src/main/java/io/appwrite/Client.kt | 17 +++- library/src/main/java/io/appwrite/Query.kt | 8 +- .../java/io/appwrite/enums/OAuthProvider.kt | 2 + .../main/java/io/appwrite/models/Locale.kt | 88 +++++++++++++++++++ .../src/main/java/io/appwrite/models/Log.kt | 2 +- .../main/java/io/appwrite/services/Account.kt | 4 +- 12 files changed, 164 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1a4849..6a099e42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## 25.3.0 + +* Added: `Client.setBearer()` for OAuth access token authentication +* Added: `Query.vectorDot()`, `Query.vectorCosine()`, `Query.vectorEuclidean()` vector similarity queries +* Added: `appwrite` value to `OAuthProvider` enum +* Added: geolocation and network fields (`city`, `timeZone`, `latitude`, `isp`, etc.) to `Locale` model +* Updated: raised `minSdk` from 21 to 23 and `compileSdk` to 37 +* Updated: upgraded dependencies (OkHttp 5.4.0, Gson 2.14.0, coroutines 1.11.0, AndroidX) + ## 25.2.0 * Added: Realtime connections now send an `x-appwrite-jwt` header for authentication. diff --git a/README.md b/README.md index 5ccbd23b..7e6a5504 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:25.2.0") +implementation("io.appwrite:sdk-for-android:25.3.0") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 25.2.0 + 25.3.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index d5244701..615848a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,8 +2,8 @@ import java.util.Properties plugins { id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.android.library") version "9.1.1" apply false - id("com.android.application") version "9.1.1" apply false + id("com.android.library") version "9.2.1" apply false + id("com.android.application") version "9.2.1" apply false } version = System.getenv("SDK_VERSION") ?: "0.0.0" diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 2675a918..d2172f2c 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -5,13 +5,13 @@ plugins { android { namespace = "io.appwrite.android" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "io.appwrite.android" - minSdk = 21 + minSdk = 23 @Suppress("DEPRECATION") - targetSdk = 36 + targetSdk = 37 versionCode = 1 versionName = "1.0" @@ -37,22 +37,22 @@ android { dependencies { implementation(project(":library")) - implementation("androidx.core:core-ktx:1.15.0") - implementation("androidx.appcompat:appcompat:1.7.0") - implementation("com.google.android.material:material:1.12.0") - implementation("androidx.constraintlayout:constraintlayout:2.2.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.8.5") - implementation("androidx.fragment:fragment-ktx:1.8.5") - implementation("androidx.navigation:navigation-ui-ktx:2.8.5") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.7") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2") - - implementation(platform("com.google.firebase:firebase-bom:33.7.0")) + implementation("androidx.core:core-ktx:1.19.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("com.google.android.material:material:1.14.0") + implementation("androidx.constraintlayout:constraintlayout:2.2.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.8") + implementation("androidx.fragment:fragment-ktx:1.8.9") + implementation("androidx.navigation:navigation-ui-ktx:2.9.8") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.11.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0") + + implementation(platform("com.google.firebase:firebase-bom:33.16.0")) implementation("com.google.firebase:firebase-messaging") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.2.1") - androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") + androidTestImplementation("androidx.test.ext:junit:1.3.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2f2958b9..221c4f98 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 68c3d398..05f2bf0d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -39,14 +39,14 @@ extra["signing.secretKeyRingFile"] = signingSecretKeyRingFile.ifEmpty { localPro android { namespace = publishGroupId - compileSdk = 36 + compileSdk = 37 buildFeatures { buildConfig = true } defaultConfig { - minSdk = 21 + minSdk = 23 buildConfigField("String", "SDK_VERSION", "\"$publishVersion\"") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -77,25 +77,25 @@ android { } dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") - api("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") + api("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0") - api("com.squareup.okhttp3:okhttp:5.3.2") - implementation("com.google.code.gson:gson:2.11.0") + api("com.squareup.okhttp3:okhttp:5.4.0") + implementation("com.google.code.gson:gson:2.14.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") - implementation("androidx.lifecycle:lifecycle-common-java8:2.8.7") - implementation("androidx.appcompat:appcompat:1.7.0") - implementation("androidx.fragment:fragment-ktx:1.8.5") - implementation("androidx.activity:activity-ktx:1.9.3") - implementation("androidx.browser:browser:1.8.0") - implementation("androidx.core:core-ktx:1.15.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0") + implementation("androidx.lifecycle:lifecycle-common-java8:2.11.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.fragment:fragment-ktx:1.8.9") + implementation("androidx.activity:activity-ktx:1.13.0") + implementation("androidx.browser:browser:1.10.0") + implementation("androidx.core:core-ktx:1.19.0") testImplementation("junit:junit:4.13.2") - testImplementation("androidx.test.ext:junit-ktx:1.2.1") - testImplementation("androidx.test:core-ktx:1.6.1") + testImplementation("androidx.test.ext:junit-ktx:1.3.0") + testImplementation("androidx.test:core-ktx:1.7.0") testImplementation("org.robolectric:robolectric:4.16.1") - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0") } afterEvaluate { diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 12b1212d..49aa347a 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -94,7 +94,7 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "25.2.0", + "x-sdk-version" to "25.3.0", "x-appwrite-response-format" to "1.9.5" ) config = mutableMapOf() @@ -131,6 +131,21 @@ class Client @JvmOverloads constructor( return this } + /** + * Set Bearer + * + * The OAuth access token to authenticate with + * + * @param {string} bearer + * + * @return this + */ + fun setBearer(value: String): Client { + config["bearer"] = value + addHeader("authorization", value) + return this + } + /** * Set Locale * diff --git a/library/src/main/java/io/appwrite/Query.kt b/library/src/main/java/io/appwrite/Query.kt index c4d5cca1..3585337b 100644 --- a/library/src/main/java/io/appwrite/Query.kt +++ b/library/src/main/java/io/appwrite/Query.kt @@ -413,6 +413,12 @@ class Query( */ fun distanceLessThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceLessThan", attribute, listOf(listOf(values, distance, meters))).toJson() + fun vectorDot(attribute: String, vector: List) = Query("vectorDot", attribute, listOf(vector)).toJson() + + fun vectorCosine(attribute: String, vector: List) = Query("vectorCosine", attribute, listOf(vector)).toJson() + + fun vectorEuclidean(attribute: String, vector: List) = Query("vectorEuclidean", attribute, listOf(vector)).toJson() + /** * Filter resources where attribute intersects with the given geometry. * @@ -498,4 +504,4 @@ class Query( } } } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt index b5de3a4c..7ef289d6 100644 --- a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt +++ b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt @@ -7,6 +7,8 @@ enum class OAuthProvider(val value: String) { AMAZON("amazon"), @SerializedName("apple") APPLE("apple"), + @SerializedName("appwrite") + APPWRITE("appwrite"), @SerializedName("auth0") AUTH0("auth0"), @SerializedName("authentik") diff --git a/library/src/main/java/io/appwrite/models/Locale.kt b/library/src/main/java/io/appwrite/models/Locale.kt index 93d28d4a..02d22526 100644 --- a/library/src/main/java/io/appwrite/models/Locale.kt +++ b/library/src/main/java/io/appwrite/models/Locale.kt @@ -49,6 +49,72 @@ data class Locale( @SerializedName("currency") val currency: String, + /** + * City + */ + @SerializedName("city") + var city: String?, + + /** + * Name of timezone + */ + @SerializedName("timeZone") + var timeZone: String?, + + /** + * Postal code + */ + @SerializedName("postalCode") + var postalCode: String?, + + /** + * Latitude + */ + @SerializedName("latitude") + var latitude: Double?, + + /** + * Longitude + */ + @SerializedName("longitude") + var longitude: Double?, + + /** + * Autonomous System Number (ASN) of the IP + */ + @SerializedName("autonomousSystemNumber") + var autonomousSystemNumber: String?, + + /** + * Organization that owns the ASN + */ + @SerializedName("autonomousSystemOrganization") + var autonomousSystemOrganization: String?, + + /** + * Internet service provider of the IP + */ + @SerializedName("isp") + var isp: String?, + + /** + * Connection type of the IP (e.g. cable, cellular, corporate) + */ + @SerializedName("connectionType") + var connectionType: String?, + + /** + * User type classification of the IP (e.g. residential, business, hosting) + */ + @SerializedName("connectionUsageType") + var connectionUsageType: String?, + + /** + * Registered organization of the IP + */ + @SerializedName("connectionOrganization") + var connectionOrganization: String?, + ) { fun toMap(): Map = mapOf( "ip" to ip as Any, @@ -58,6 +124,17 @@ data class Locale( "continent" to continent as Any, "eu" to eu as Any, "currency" to currency as Any, + "city" to city as Any, + "timeZone" to timeZone as Any, + "postalCode" to postalCode as Any, + "latitude" to latitude as Any, + "longitude" to longitude as Any, + "autonomousSystemNumber" to autonomousSystemNumber as Any, + "autonomousSystemOrganization" to autonomousSystemOrganization as Any, + "isp" to isp as Any, + "connectionType" to connectionType as Any, + "connectionUsageType" to connectionUsageType as Any, + "connectionOrganization" to connectionOrganization as Any, ) companion object { @@ -73,6 +150,17 @@ data class Locale( continent = map["continent"] as String, eu = map["eu"] as Boolean, currency = map["currency"] as String, + city = map["city"] as? String, + timeZone = map["timeZone"] as? String, + postalCode = map["postalCode"] as? String, + latitude = (map["latitude"] as? Number)?.toDouble(), + longitude = (map["longitude"] as? Number)?.toDouble(), + autonomousSystemNumber = map["autonomousSystemNumber"] as? String, + autonomousSystemOrganization = map["autonomousSystemOrganization"] as? String, + isp = map["isp"] as? String, + connectionType = map["connectionType"] as? String, + connectionUsageType = map["connectionUsageType"] as? String, + connectionOrganization = map["connectionOrganization"] as? String, ) } } \ No newline at end of file diff --git a/library/src/main/java/io/appwrite/models/Log.kt b/library/src/main/java/io/appwrite/models/Log.kt index 47572218..eaa69687 100644 --- a/library/src/main/java/io/appwrite/models/Log.kt +++ b/library/src/main/java/io/appwrite/models/Log.kt @@ -38,7 +38,7 @@ data class Log( val mode: String, /** - * User type who triggered the audit log. Possible values: user, admin, guest, keyProject, keyAccount, keyOrganization. + * User type who triggered the audit log. Possible values: user, admin, guest, hidden, keyProject, keyAccount, keyOrganization. */ @SerializedName("userType") val userType: String, diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index 25c44630..633572de 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -1566,7 +1566,7 @@ class Account(client: Client) : Service(client) { * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -2066,7 +2066,7 @@ class Account(client: Client) : Service(client) { * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. From 9fe9907f19c501305919d7e1c96888007c9b9844 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Jul 2026 12:09:53 +0530 Subject: [PATCH 02/12] chore: update Android SDK to 25.3.0 --- gradle/wrapper/gradle-wrapper.properties | 2 +- library/src/main/java/io/appwrite/Client.kt | 2 +- library/src/main/java/io/appwrite/Query.kt | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 221c4f98..52ad5e71 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 49aa347a..d5ff9ad1 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -142,7 +142,7 @@ class Client @JvmOverloads constructor( */ fun setBearer(value: String): Client { config["bearer"] = value - addHeader("authorization", value) + addHeader("authorization", "Bearer $value") return this } diff --git a/library/src/main/java/io/appwrite/Query.kt b/library/src/main/java/io/appwrite/Query.kt index 3585337b..e6ba5b60 100644 --- a/library/src/main/java/io/appwrite/Query.kt +++ b/library/src/main/java/io/appwrite/Query.kt @@ -413,10 +413,31 @@ class Query( */ fun distanceLessThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceLessThan", attribute, listOf(listOf(values, distance, meters))).toJson() + /** + * Filter resources by dot product similarity between attribute and the given vector. + * + * @param attribute The attribute to filter on. + * @param vector The query vector. + * @returns The query string. + */ fun vectorDot(attribute: String, vector: List) = Query("vectorDot", attribute, listOf(vector)).toJson() + /** + * Filter resources by cosine similarity between attribute and the given vector. + * + * @param attribute The attribute to filter on. + * @param vector The query vector. + * @returns The query string. + */ fun vectorCosine(attribute: String, vector: List) = Query("vectorCosine", attribute, listOf(vector)).toJson() + /** + * Filter resources by Euclidean distance between attribute and the given vector. + * + * @param attribute The attribute to filter on. + * @param vector The query vector. + * @returns The query string. + */ fun vectorEuclidean(attribute: String, vector: List) = Query("vectorEuclidean", attribute, listOf(vector)).toJson() /** From c5a3ef8e2d2d8f4acf1cbb8b7e9aafd46fa396e6 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Jul 2026 12:32:37 +0530 Subject: [PATCH 03/12] chore: update Android SDK to 26.0.0 --- CHANGELOG.md | 5 +++-- README.md | 4 ++-- library/src/main/java/io/appwrite/Client.kt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a099e42..d73d8efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Change Log -## 25.3.0 +## 26.0.0 +* Breaking: raised `minSdk` from 21 to 23, dropping support for Android 5.0/5.1 (API 21-22) * Added: `Client.setBearer()` for OAuth access token authentication * Added: `Query.vectorDot()`, `Query.vectorCosine()`, `Query.vectorEuclidean()` vector similarity queries * Added: `appwrite` value to `OAuthProvider` enum * Added: geolocation and network fields (`city`, `timeZone`, `latitude`, `isp`, etc.) to `Locale` model -* Updated: raised `minSdk` from 21 to 23 and `compileSdk` to 37 +* Updated: raised `compileSdk` to 37 * Updated: upgraded dependencies (OkHttp 5.4.0, Gson 2.14.0, coroutines 1.11.0, AndroidX) ## 25.2.0 diff --git a/README.md b/README.md index 7e6a5504..196f9c42 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:25.3.0") +implementation("io.appwrite:sdk-for-android:26.0.0") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 25.3.0 + 26.0.0 ``` diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index d5ff9ad1..2610f62d 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -94,7 +94,7 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "25.3.0", + "x-sdk-version" to "26.0.0", "x-appwrite-response-format" to "1.9.5" ) config = mutableMapOf() From 97c946171857a6a7901420134a49d6b3526574ae Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Jul 2026 13:02:20 +0530 Subject: [PATCH 04/12] chore: update Android SDK to 26.0.0 --- .../java/io/appwrite/models/AlgoArgon2.kt | 4 +-- .../java/io/appwrite/models/AlgoBcrypt.kt | 4 +-- .../main/java/io/appwrite/models/AlgoMd5.kt | 4 +-- .../java/io/appwrite/models/AlgoPhpass.kt | 4 +-- .../java/io/appwrite/models/AlgoScrypt.kt | 4 +-- .../io/appwrite/models/AlgoScryptModified.kt | 4 +-- .../main/java/io/appwrite/models/AlgoSha.kt | 4 +-- .../main/java/io/appwrite/models/Continent.kt | 4 +-- .../java/io/appwrite/models/ContinentList.kt | 4 +-- .../main/java/io/appwrite/models/Country.kt | 4 +-- .../java/io/appwrite/models/CountryList.kt | 4 +-- .../main/java/io/appwrite/models/Currency.kt | 4 +-- .../java/io/appwrite/models/CurrencyList.kt | 4 +-- .../main/java/io/appwrite/models/Document.kt | 4 +-- .../java/io/appwrite/models/DocumentList.kt | 4 +-- .../main/java/io/appwrite/models/Execution.kt | 6 ++--- .../java/io/appwrite/models/ExecutionList.kt | 4 +-- .../src/main/java/io/appwrite/models/File.kt | 4 +-- .../main/java/io/appwrite/models/FileList.kt | 4 +-- .../main/java/io/appwrite/models/Headers.kt | 4 +-- .../main/java/io/appwrite/models/Identity.kt | 4 +-- .../java/io/appwrite/models/IdentityList.kt | 4 +-- .../src/main/java/io/appwrite/models/Jwt.kt | 4 +-- .../main/java/io/appwrite/models/Language.kt | 4 +-- .../java/io/appwrite/models/LanguageList.kt | 4 +-- .../main/java/io/appwrite/models/Locale.kt | 26 +++++++++---------- .../java/io/appwrite/models/LocaleCode.kt | 4 +-- .../java/io/appwrite/models/LocaleCodeList.kt | 4 +-- .../src/main/java/io/appwrite/models/Log.kt | 4 +-- .../main/java/io/appwrite/models/LogList.kt | 4 +-- .../java/io/appwrite/models/Membership.kt | 4 +-- .../java/io/appwrite/models/MembershipList.kt | 4 +-- .../java/io/appwrite/models/MfaChallenge.kt | 4 +-- .../java/io/appwrite/models/MfaFactors.kt | 4 +-- .../io/appwrite/models/MfaRecoveryCodes.kt | 4 +-- .../main/java/io/appwrite/models/MfaType.kt | 4 +-- .../src/main/java/io/appwrite/models/Phone.kt | 4 +-- .../main/java/io/appwrite/models/PhoneList.kt | 4 +-- .../java/io/appwrite/models/Preferences.kt | 4 +-- .../main/java/io/appwrite/models/Presence.kt | 10 +++---- .../java/io/appwrite/models/PresenceList.kt | 4 +-- .../src/main/java/io/appwrite/models/Row.kt | 4 +-- .../main/java/io/appwrite/models/RowList.kt | 4 +-- .../main/java/io/appwrite/models/Session.kt | 4 +-- .../java/io/appwrite/models/SessionList.kt | 4 +-- .../java/io/appwrite/models/Subscriber.kt | 4 +-- .../main/java/io/appwrite/models/Target.kt | 6 ++--- .../src/main/java/io/appwrite/models/Team.kt | 4 +-- .../main/java/io/appwrite/models/TeamList.kt | 4 +-- .../src/main/java/io/appwrite/models/Token.kt | 4 +-- .../java/io/appwrite/models/Transaction.kt | 4 +-- .../io/appwrite/models/TransactionList.kt | 4 +-- .../src/main/java/io/appwrite/models/User.kt | 24 ++++++++--------- 53 files changed, 132 insertions(+), 132 deletions(-) diff --git a/library/src/main/java/io/appwrite/models/AlgoArgon2.kt b/library/src/main/java/io/appwrite/models/AlgoArgon2.kt index 62302485..dee8c51e 100644 --- a/library/src/main/java/io/appwrite/models/AlgoArgon2.kt +++ b/library/src/main/java/io/appwrite/models/AlgoArgon2.kt @@ -32,7 +32,7 @@ data class AlgoArgon2( val threads: Long, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "type" to type as Any, "memoryCost" to memoryCost as Any, "timeCost" to timeCost as Any, @@ -51,4 +51,4 @@ data class AlgoArgon2( threads = (map["threads"] as Number).toLong(), ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt b/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt index 24e74b8f..a09e0fa8 100644 --- a/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt +++ b/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt @@ -14,7 +14,7 @@ data class AlgoBcrypt( val type: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "type" to type as Any, ) @@ -27,4 +27,4 @@ data class AlgoBcrypt( type = map["type"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/AlgoMd5.kt b/library/src/main/java/io/appwrite/models/AlgoMd5.kt index 71baf517..21b4d2c3 100644 --- a/library/src/main/java/io/appwrite/models/AlgoMd5.kt +++ b/library/src/main/java/io/appwrite/models/AlgoMd5.kt @@ -14,7 +14,7 @@ data class AlgoMd5( val type: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "type" to type as Any, ) @@ -27,4 +27,4 @@ data class AlgoMd5( type = map["type"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/AlgoPhpass.kt b/library/src/main/java/io/appwrite/models/AlgoPhpass.kt index d89b8cff..839b70b8 100644 --- a/library/src/main/java/io/appwrite/models/AlgoPhpass.kt +++ b/library/src/main/java/io/appwrite/models/AlgoPhpass.kt @@ -14,7 +14,7 @@ data class AlgoPhpass( val type: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "type" to type as Any, ) @@ -27,4 +27,4 @@ data class AlgoPhpass( type = map["type"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/AlgoScrypt.kt b/library/src/main/java/io/appwrite/models/AlgoScrypt.kt index 2e4ae1e8..0d8af851 100644 --- a/library/src/main/java/io/appwrite/models/AlgoScrypt.kt +++ b/library/src/main/java/io/appwrite/models/AlgoScrypt.kt @@ -38,7 +38,7 @@ data class AlgoScrypt( val length: Long, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "type" to type as Any, "costCpu" to costCpu as Any, "costMemory" to costMemory as Any, @@ -59,4 +59,4 @@ data class AlgoScrypt( length = (map["length"] as Number).toLong(), ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt b/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt index 3807d6dd..f0ac3517 100644 --- a/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt +++ b/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt @@ -32,7 +32,7 @@ data class AlgoScryptModified( val signerKey: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "type" to type as Any, "salt" to salt as Any, "saltSeparator" to saltSeparator as Any, @@ -51,4 +51,4 @@ data class AlgoScryptModified( signerKey = map["signerKey"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/AlgoSha.kt b/library/src/main/java/io/appwrite/models/AlgoSha.kt index 4209a0ef..a366d584 100644 --- a/library/src/main/java/io/appwrite/models/AlgoSha.kt +++ b/library/src/main/java/io/appwrite/models/AlgoSha.kt @@ -14,7 +14,7 @@ data class AlgoSha( val type: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "type" to type as Any, ) @@ -27,4 +27,4 @@ data class AlgoSha( type = map["type"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Continent.kt b/library/src/main/java/io/appwrite/models/Continent.kt index 818c15ba..f86090b0 100644 --- a/library/src/main/java/io/appwrite/models/Continent.kt +++ b/library/src/main/java/io/appwrite/models/Continent.kt @@ -20,7 +20,7 @@ data class Continent( val code: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "name" to name as Any, "code" to code as Any, ) @@ -35,4 +35,4 @@ data class Continent( code = map["code"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/ContinentList.kt b/library/src/main/java/io/appwrite/models/ContinentList.kt index 789acff0..99476f26 100644 --- a/library/src/main/java/io/appwrite/models/ContinentList.kt +++ b/library/src/main/java/io/appwrite/models/ContinentList.kt @@ -20,7 +20,7 @@ data class ContinentList( val continents: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "continents" to continents.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class ContinentList( continents = (map["continents"] as List>).map { Continent.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Country.kt b/library/src/main/java/io/appwrite/models/Country.kt index 92ab89ae..e1d818c4 100644 --- a/library/src/main/java/io/appwrite/models/Country.kt +++ b/library/src/main/java/io/appwrite/models/Country.kt @@ -20,7 +20,7 @@ data class Country( val code: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "name" to name as Any, "code" to code as Any, ) @@ -35,4 +35,4 @@ data class Country( code = map["code"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/CountryList.kt b/library/src/main/java/io/appwrite/models/CountryList.kt index 47c0e724..26e162ff 100644 --- a/library/src/main/java/io/appwrite/models/CountryList.kt +++ b/library/src/main/java/io/appwrite/models/CountryList.kt @@ -20,7 +20,7 @@ data class CountryList( val countries: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "countries" to countries.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class CountryList( countries = (map["countries"] as List>).map { Country.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Currency.kt b/library/src/main/java/io/appwrite/models/Currency.kt index 96b65d03..421d1601 100644 --- a/library/src/main/java/io/appwrite/models/Currency.kt +++ b/library/src/main/java/io/appwrite/models/Currency.kt @@ -50,7 +50,7 @@ data class Currency( val namePlural: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "symbol" to symbol as Any, "name" to name as Any, "symbolNative" to symbolNative as Any, @@ -75,4 +75,4 @@ data class Currency( namePlural = map["namePlural"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/CurrencyList.kt b/library/src/main/java/io/appwrite/models/CurrencyList.kt index 5a57a02d..fd391d36 100644 --- a/library/src/main/java/io/appwrite/models/CurrencyList.kt +++ b/library/src/main/java/io/appwrite/models/CurrencyList.kt @@ -20,7 +20,7 @@ data class CurrencyList( val currencies: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "currencies" to currencies.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class CurrencyList( currencies = (map["currencies"] as List>).map { Currency.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Document.kt b/library/src/main/java/io/appwrite/models/Document.kt index c55e6d26..20e803e1 100644 --- a/library/src/main/java/io/appwrite/models/Document.kt +++ b/library/src/main/java/io/appwrite/models/Document.kt @@ -55,7 +55,7 @@ data class Document( @SerializedName("data") val data: T ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$sequence" to sequence as Any, "\$collectionId" to collectionId as Any, @@ -102,4 +102,4 @@ data class Document( data = map["data"]?.jsonCast(to = nestedType) ?: map.jsonCast(to = nestedType) ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/DocumentList.kt b/library/src/main/java/io/appwrite/models/DocumentList.kt index c26cd26c..d8741eab 100644 --- a/library/src/main/java/io/appwrite/models/DocumentList.kt +++ b/library/src/main/java/io/appwrite/models/DocumentList.kt @@ -20,7 +20,7 @@ data class DocumentList( val documents: List>, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "documents" to documents.map { it.toMap() } as Any, ) @@ -43,4 +43,4 @@ data class DocumentList( documents = (map["documents"] as List>).map { Document.from(map = it, nestedType) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Execution.kt b/library/src/main/java/io/appwrite/models/Execution.kt index e77ee1fd..f021fabe 100644 --- a/library/src/main/java/io/appwrite/models/Execution.kt +++ b/library/src/main/java/io/appwrite/models/Execution.kt @@ -118,7 +118,7 @@ data class Execution( var scheduledAt: String?, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, @@ -136,7 +136,7 @@ data class Execution( "logs" to logs as Any, "errors" to errors as Any, "duration" to duration as Any, - "scheduledAt" to scheduledAt as Any, + "scheduledAt" to scheduledAt as Any?, ) companion object { @@ -165,4 +165,4 @@ data class Execution( scheduledAt = map["scheduledAt"] as? String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/ExecutionList.kt b/library/src/main/java/io/appwrite/models/ExecutionList.kt index a976e783..2b208bac 100644 --- a/library/src/main/java/io/appwrite/models/ExecutionList.kt +++ b/library/src/main/java/io/appwrite/models/ExecutionList.kt @@ -20,7 +20,7 @@ data class ExecutionList( val executions: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "executions" to executions.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class ExecutionList( executions = (map["executions"] as List>).map { Execution.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/File.kt b/library/src/main/java/io/appwrite/models/File.kt index 26a716ab..760c325f 100644 --- a/library/src/main/java/io/appwrite/models/File.kt +++ b/library/src/main/java/io/appwrite/models/File.kt @@ -92,7 +92,7 @@ data class File( val compression: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "bucketId" to bucketId as Any, "\$createdAt" to createdAt as Any, @@ -131,4 +131,4 @@ data class File( compression = map["compression"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/FileList.kt b/library/src/main/java/io/appwrite/models/FileList.kt index dfdd212a..60a1b676 100644 --- a/library/src/main/java/io/appwrite/models/FileList.kt +++ b/library/src/main/java/io/appwrite/models/FileList.kt @@ -20,7 +20,7 @@ data class FileList( val files: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "files" to files.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class FileList( files = (map["files"] as List>).map { File.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Headers.kt b/library/src/main/java/io/appwrite/models/Headers.kt index 230d6a60..1587bd17 100644 --- a/library/src/main/java/io/appwrite/models/Headers.kt +++ b/library/src/main/java/io/appwrite/models/Headers.kt @@ -20,7 +20,7 @@ data class Headers( val value: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "name" to name as Any, "value" to value as Any, ) @@ -35,4 +35,4 @@ data class Headers( value = map["value"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Identity.kt b/library/src/main/java/io/appwrite/models/Identity.kt index 70ff5ea5..89e92b9d 100644 --- a/library/src/main/java/io/appwrite/models/Identity.kt +++ b/library/src/main/java/io/appwrite/models/Identity.kt @@ -68,7 +68,7 @@ data class Identity( val providerRefreshToken: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, @@ -99,4 +99,4 @@ data class Identity( providerRefreshToken = map["providerRefreshToken"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/IdentityList.kt b/library/src/main/java/io/appwrite/models/IdentityList.kt index 49c3346c..3dfe554d 100644 --- a/library/src/main/java/io/appwrite/models/IdentityList.kt +++ b/library/src/main/java/io/appwrite/models/IdentityList.kt @@ -20,7 +20,7 @@ data class IdentityList( val identities: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "identities" to identities.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class IdentityList( identities = (map["identities"] as List>).map { Identity.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Jwt.kt b/library/src/main/java/io/appwrite/models/Jwt.kt index f55c1032..0a62fda7 100644 --- a/library/src/main/java/io/appwrite/models/Jwt.kt +++ b/library/src/main/java/io/appwrite/models/Jwt.kt @@ -14,7 +14,7 @@ data class Jwt( val jwt: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "jwt" to jwt as Any, ) @@ -27,4 +27,4 @@ data class Jwt( jwt = map["jwt"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Language.kt b/library/src/main/java/io/appwrite/models/Language.kt index 8860aad2..34ff473e 100644 --- a/library/src/main/java/io/appwrite/models/Language.kt +++ b/library/src/main/java/io/appwrite/models/Language.kt @@ -26,7 +26,7 @@ data class Language( val nativeName: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "name" to name as Any, "code" to code as Any, "nativeName" to nativeName as Any, @@ -43,4 +43,4 @@ data class Language( nativeName = map["nativeName"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/LanguageList.kt b/library/src/main/java/io/appwrite/models/LanguageList.kt index 1aac2f7a..b68315fc 100644 --- a/library/src/main/java/io/appwrite/models/LanguageList.kt +++ b/library/src/main/java/io/appwrite/models/LanguageList.kt @@ -20,7 +20,7 @@ data class LanguageList( val languages: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "languages" to languages.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class LanguageList( languages = (map["languages"] as List>).map { Language.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Locale.kt b/library/src/main/java/io/appwrite/models/Locale.kt index 02d22526..8fd26bc1 100644 --- a/library/src/main/java/io/appwrite/models/Locale.kt +++ b/library/src/main/java/io/appwrite/models/Locale.kt @@ -116,7 +116,7 @@ data class Locale( var connectionOrganization: String?, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "ip" to ip as Any, "countryCode" to countryCode as Any, "country" to country as Any, @@ -124,17 +124,17 @@ data class Locale( "continent" to continent as Any, "eu" to eu as Any, "currency" to currency as Any, - "city" to city as Any, - "timeZone" to timeZone as Any, - "postalCode" to postalCode as Any, - "latitude" to latitude as Any, - "longitude" to longitude as Any, - "autonomousSystemNumber" to autonomousSystemNumber as Any, - "autonomousSystemOrganization" to autonomousSystemOrganization as Any, - "isp" to isp as Any, - "connectionType" to connectionType as Any, - "connectionUsageType" to connectionUsageType as Any, - "connectionOrganization" to connectionOrganization as Any, + "city" to city as Any?, + "timeZone" to timeZone as Any?, + "postalCode" to postalCode as Any?, + "latitude" to latitude as Any?, + "longitude" to longitude as Any?, + "autonomousSystemNumber" to autonomousSystemNumber as Any?, + "autonomousSystemOrganization" to autonomousSystemOrganization as Any?, + "isp" to isp as Any?, + "connectionType" to connectionType as Any?, + "connectionUsageType" to connectionUsageType as Any?, + "connectionOrganization" to connectionOrganization as Any?, ) companion object { @@ -163,4 +163,4 @@ data class Locale( connectionOrganization = map["connectionOrganization"] as? String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/LocaleCode.kt b/library/src/main/java/io/appwrite/models/LocaleCode.kt index ed886b7b..b16bf4a4 100644 --- a/library/src/main/java/io/appwrite/models/LocaleCode.kt +++ b/library/src/main/java/io/appwrite/models/LocaleCode.kt @@ -20,7 +20,7 @@ data class LocaleCode( val name: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "code" to code as Any, "name" to name as Any, ) @@ -35,4 +35,4 @@ data class LocaleCode( name = map["name"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/LocaleCodeList.kt b/library/src/main/java/io/appwrite/models/LocaleCodeList.kt index 7949b573..912cc0b8 100644 --- a/library/src/main/java/io/appwrite/models/LocaleCodeList.kt +++ b/library/src/main/java/io/appwrite/models/LocaleCodeList.kt @@ -20,7 +20,7 @@ data class LocaleCodeList( val localeCodes: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "localeCodes" to localeCodes.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class LocaleCodeList( localeCodes = (map["localeCodes"] as List>).map { LocaleCode.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Log.kt b/library/src/main/java/io/appwrite/models/Log.kt index eaa69687..fdde90d8 100644 --- a/library/src/main/java/io/appwrite/models/Log.kt +++ b/library/src/main/java/io/appwrite/models/Log.kt @@ -140,7 +140,7 @@ data class Log( val countryName: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "event" to event as Any, "userId" to userId as Any, "userEmail" to userEmail as Any, @@ -195,4 +195,4 @@ data class Log( countryName = map["countryName"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/LogList.kt b/library/src/main/java/io/appwrite/models/LogList.kt index c88451cd..22f1df59 100644 --- a/library/src/main/java/io/appwrite/models/LogList.kt +++ b/library/src/main/java/io/appwrite/models/LogList.kt @@ -20,7 +20,7 @@ data class LogList( val logs: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "logs" to logs.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class LogList( logs = (map["logs"] as List>).map { Log.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Membership.kt b/library/src/main/java/io/appwrite/models/Membership.kt index afcbdf77..ab0088b4 100644 --- a/library/src/main/java/io/appwrite/models/Membership.kt +++ b/library/src/main/java/io/appwrite/models/Membership.kt @@ -98,7 +98,7 @@ data class Membership( val roles: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, @@ -139,4 +139,4 @@ data class Membership( roles = map["roles"] as List, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/MembershipList.kt b/library/src/main/java/io/appwrite/models/MembershipList.kt index b2bea4e8..60d2677c 100644 --- a/library/src/main/java/io/appwrite/models/MembershipList.kt +++ b/library/src/main/java/io/appwrite/models/MembershipList.kt @@ -20,7 +20,7 @@ data class MembershipList( val memberships: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "memberships" to memberships.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class MembershipList( memberships = (map["memberships"] as List>).map { Membership.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/MfaChallenge.kt b/library/src/main/java/io/appwrite/models/MfaChallenge.kt index 82b1d18b..2eacbc92 100644 --- a/library/src/main/java/io/appwrite/models/MfaChallenge.kt +++ b/library/src/main/java/io/appwrite/models/MfaChallenge.kt @@ -32,7 +32,7 @@ data class MfaChallenge( val expire: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "userId" to userId as Any, @@ -51,4 +51,4 @@ data class MfaChallenge( expire = map["expire"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/MfaFactors.kt b/library/src/main/java/io/appwrite/models/MfaFactors.kt index ddde40c8..ac1a6a7a 100644 --- a/library/src/main/java/io/appwrite/models/MfaFactors.kt +++ b/library/src/main/java/io/appwrite/models/MfaFactors.kt @@ -32,7 +32,7 @@ data class MfaFactors( val recoveryCode: Boolean, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "totp" to totp as Any, "phone" to phone as Any, "email" to email as Any, @@ -51,4 +51,4 @@ data class MfaFactors( recoveryCode = map["recoveryCode"] as Boolean, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt b/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt index 3fab41a4..1bf2292f 100644 --- a/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt +++ b/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt @@ -14,7 +14,7 @@ data class MfaRecoveryCodes( val recoveryCodes: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "recoveryCodes" to recoveryCodes as Any, ) @@ -27,4 +27,4 @@ data class MfaRecoveryCodes( recoveryCodes = map["recoveryCodes"] as List, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/MfaType.kt b/library/src/main/java/io/appwrite/models/MfaType.kt index 9c784a9b..bb7f4667 100644 --- a/library/src/main/java/io/appwrite/models/MfaType.kt +++ b/library/src/main/java/io/appwrite/models/MfaType.kt @@ -20,7 +20,7 @@ data class MfaType( val uri: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "secret" to secret as Any, "uri" to uri as Any, ) @@ -35,4 +35,4 @@ data class MfaType( uri = map["uri"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Phone.kt b/library/src/main/java/io/appwrite/models/Phone.kt index 28aea2d2..88d19af3 100644 --- a/library/src/main/java/io/appwrite/models/Phone.kt +++ b/library/src/main/java/io/appwrite/models/Phone.kt @@ -26,7 +26,7 @@ data class Phone( val countryName: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "code" to code as Any, "countryCode" to countryCode as Any, "countryName" to countryName as Any, @@ -43,4 +43,4 @@ data class Phone( countryName = map["countryName"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/PhoneList.kt b/library/src/main/java/io/appwrite/models/PhoneList.kt index 5f7c22f8..7b7507fd 100644 --- a/library/src/main/java/io/appwrite/models/PhoneList.kt +++ b/library/src/main/java/io/appwrite/models/PhoneList.kt @@ -20,7 +20,7 @@ data class PhoneList( val phones: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "phones" to phones.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class PhoneList( phones = (map["phones"] as List>).map { Phone.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Preferences.kt b/library/src/main/java/io/appwrite/models/Preferences.kt index 3d41ad19..dc8e11b5 100644 --- a/library/src/main/java/io/appwrite/models/Preferences.kt +++ b/library/src/main/java/io/appwrite/models/Preferences.kt @@ -13,7 +13,7 @@ data class Preferences( @SerializedName("data") val data: T ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "data" to data!!.jsonCast(to = Map::class.java) ) @@ -32,4 +32,4 @@ data class Preferences( data = map["data"]?.jsonCast(to = nestedType) ?: map.jsonCast(to = nestedType) ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Presence.kt b/library/src/main/java/io/appwrite/models/Presence.kt index 48cde361..0b25aec3 100644 --- a/library/src/main/java/io/appwrite/models/Presence.kt +++ b/library/src/main/java/io/appwrite/models/Presence.kt @@ -62,16 +62,16 @@ data class Presence( var metadata: Any?, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, "\$permissions" to permissions as Any, "userId" to userId as Any, - "status" to status as Any, + "status" to status as Any?, "source" to source as Any, - "expiresAt" to expiresAt as Any, - "metadata" to metadata as Any, + "expiresAt" to expiresAt as Any?, + "metadata" to metadata as Any?, ) companion object { @@ -91,4 +91,4 @@ data class Presence( metadata = map["metadata"] as? Any, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/PresenceList.kt b/library/src/main/java/io/appwrite/models/PresenceList.kt index b5b9a62a..58609304 100644 --- a/library/src/main/java/io/appwrite/models/PresenceList.kt +++ b/library/src/main/java/io/appwrite/models/PresenceList.kt @@ -20,7 +20,7 @@ data class PresenceList( val presences: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "presences" to presences.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class PresenceList( presences = (map["presences"] as List>).map { Presence.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Row.kt b/library/src/main/java/io/appwrite/models/Row.kt index 488ec7bc..1f190104 100644 --- a/library/src/main/java/io/appwrite/models/Row.kt +++ b/library/src/main/java/io/appwrite/models/Row.kt @@ -55,7 +55,7 @@ data class Row( @SerializedName("data") val data: T ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$sequence" to sequence as Any, "\$tableId" to tableId as Any, @@ -102,4 +102,4 @@ data class Row( data = map["data"]?.jsonCast(to = nestedType) ?: map.jsonCast(to = nestedType) ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/RowList.kt b/library/src/main/java/io/appwrite/models/RowList.kt index 5e99cd73..f9c8f61e 100644 --- a/library/src/main/java/io/appwrite/models/RowList.kt +++ b/library/src/main/java/io/appwrite/models/RowList.kt @@ -20,7 +20,7 @@ data class RowList( val rows: List>, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "rows" to rows.map { it.toMap() } as Any, ) @@ -43,4 +43,4 @@ data class RowList( rows = (map["rows"] as List>).map { Row.from(map = it, nestedType) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Session.kt b/library/src/main/java/io/appwrite/models/Session.kt index 02dbb289..7bda2aeb 100644 --- a/library/src/main/java/io/appwrite/models/Session.kt +++ b/library/src/main/java/io/appwrite/models/Session.kt @@ -182,7 +182,7 @@ data class Session( val mfaUpdatedAt: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, @@ -251,4 +251,4 @@ data class Session( mfaUpdatedAt = map["mfaUpdatedAt"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/SessionList.kt b/library/src/main/java/io/appwrite/models/SessionList.kt index 22fba69e..e7368cb8 100644 --- a/library/src/main/java/io/appwrite/models/SessionList.kt +++ b/library/src/main/java/io/appwrite/models/SessionList.kt @@ -20,7 +20,7 @@ data class SessionList( val sessions: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "sessions" to sessions.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class SessionList( sessions = (map["sessions"] as List>).map { Session.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Subscriber.kt b/library/src/main/java/io/appwrite/models/Subscriber.kt index 1920691e..467920fe 100644 --- a/library/src/main/java/io/appwrite/models/Subscriber.kt +++ b/library/src/main/java/io/appwrite/models/Subscriber.kt @@ -62,7 +62,7 @@ data class Subscriber( val providerType: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, @@ -91,4 +91,4 @@ data class Subscriber( providerType = map["providerType"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Target.kt b/library/src/main/java/io/appwrite/models/Target.kt index 48d96504..caa87117 100644 --- a/library/src/main/java/io/appwrite/models/Target.kt +++ b/library/src/main/java/io/appwrite/models/Target.kt @@ -62,13 +62,13 @@ data class Target( val expired: Boolean, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, "name" to name as Any, "userId" to userId as Any, - "providerId" to providerId as Any, + "providerId" to providerId as Any?, "providerType" to providerType as Any, "identifier" to identifier as Any, "expired" to expired as Any, @@ -91,4 +91,4 @@ data class Target( expired = map["expired"] as Boolean, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Team.kt b/library/src/main/java/io/appwrite/models/Team.kt index 0723b904..6cd7dbc9 100644 --- a/library/src/main/java/io/appwrite/models/Team.kt +++ b/library/src/main/java/io/appwrite/models/Team.kt @@ -44,7 +44,7 @@ data class Team( val prefs: Preferences, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, @@ -83,4 +83,4 @@ data class Team( prefs = Preferences.from(map = map["prefs"] as Map, nestedType), ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/TeamList.kt b/library/src/main/java/io/appwrite/models/TeamList.kt index c95cc565..8e490f51 100644 --- a/library/src/main/java/io/appwrite/models/TeamList.kt +++ b/library/src/main/java/io/appwrite/models/TeamList.kt @@ -20,7 +20,7 @@ data class TeamList( val teams: List>, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "teams" to teams.map { it.toMap() } as Any, ) @@ -43,4 +43,4 @@ data class TeamList( teams = (map["teams"] as List>).map { Team.from(map = it, nestedType) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Token.kt b/library/src/main/java/io/appwrite/models/Token.kt index e36d060e..b4e35161 100644 --- a/library/src/main/java/io/appwrite/models/Token.kt +++ b/library/src/main/java/io/appwrite/models/Token.kt @@ -44,7 +44,7 @@ data class Token( val phrase: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "userId" to userId as Any, @@ -67,4 +67,4 @@ data class Token( phrase = map["phrase"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Transaction.kt b/library/src/main/java/io/appwrite/models/Transaction.kt index baf32d31..f9e86be0 100644 --- a/library/src/main/java/io/appwrite/models/Transaction.kt +++ b/library/src/main/java/io/appwrite/models/Transaction.kt @@ -44,7 +44,7 @@ data class Transaction( val expiresAt: String, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, @@ -67,4 +67,4 @@ data class Transaction( expiresAt = map["expiresAt"] as String, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/TransactionList.kt b/library/src/main/java/io/appwrite/models/TransactionList.kt index 1233ed49..c64ca151 100644 --- a/library/src/main/java/io/appwrite/models/TransactionList.kt +++ b/library/src/main/java/io/appwrite/models/TransactionList.kt @@ -20,7 +20,7 @@ data class TransactionList( val transactions: List, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "total" to total as Any, "transactions" to transactions.map { it.toMap() } as Any, ) @@ -35,4 +35,4 @@ data class TransactionList( transactions = (map["transactions"] as List>).map { Transaction.from(map = it) }, ) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/User.kt b/library/src/main/java/io/appwrite/models/User.kt index ff66ba72..fd3d3760 100644 --- a/library/src/main/java/io/appwrite/models/User.kt +++ b/library/src/main/java/io/appwrite/models/User.kt @@ -164,14 +164,14 @@ data class User( var impersonatorUserId: String?, ) { - fun toMap(): Map = mapOf( + fun toMap(): Map = mapOf( "\$id" to id as Any, "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, "name" to name as Any, - "password" to password as Any, - "hash" to hash as Any, - "hashOptions" to hashOptions as Any, + "password" to password as Any?, + "hash" to hash as Any?, + "hashOptions" to hashOptions as Any?, "registration" to registration as Any, "status" to status as Any, "labels" to labels as Any, @@ -179,18 +179,18 @@ data class User( "email" to email as Any, "phone" to phone as Any, "emailVerification" to emailVerification as Any, - "emailCanonical" to emailCanonical as Any, - "emailIsFree" to emailIsFree as Any, - "emailIsDisposable" to emailIsDisposable as Any, - "emailIsCorporate" to emailIsCorporate as Any, - "emailIsCanonical" to emailIsCanonical as Any, + "emailCanonical" to emailCanonical as Any?, + "emailIsFree" to emailIsFree as Any?, + "emailIsDisposable" to emailIsDisposable as Any?, + "emailIsCorporate" to emailIsCorporate as Any?, + "emailIsCanonical" to emailIsCanonical as Any?, "phoneVerification" to phoneVerification as Any, "mfa" to mfa as Any, "prefs" to prefs.toMap() as Any, "targets" to targets.map { it.toMap() } as Any, "accessedAt" to accessedAt as Any, - "impersonator" to impersonator as Any, - "impersonatorUserId" to impersonatorUserId as Any, + "impersonator" to impersonator as Any?, + "impersonatorUserId" to impersonatorUserId as Any?, ) companion object { @@ -283,4 +283,4 @@ data class User( impersonatorUserId = map["impersonatorUserId"] as? String, ) } -} \ No newline at end of file +} From 236e0858196f0681a9878a41c33362aafc0251ee Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 25 Aug 2026 19:08:36 +0530 Subject: [PATCH 05/12] chore: update Android SDK to 27.0.0 --- .editorconfig | 39 ++ .github/workflows/publish.yml | 4 +- CHANGELOG.md | 12 + README.md | 6 +- build.gradle.kts | 4 +- .../java/account/create-anonymous-session.md | 2 + .../account/create-email-password-session.md | 2 + .../java/account/create-email-token.md | 2 + .../java/account/create-email-verification.md | 2 + docs/examples/java/account/create-jwt.md | 2 + .../java/account/create-magic-url-token.md | 2 + .../java/account/create-mfa-authenticator.md | 2 + .../java/account/create-mfa-challenge.md | 2 + .../java/account/create-mfa-recovery-codes.md | 2 + .../java/account/create-o-auth-2-session.md | 2 + .../java/account/create-o-auth-2-token.md | 2 + .../java/account/create-phone-token.md | 2 + .../java/account/create-phone-verification.md | 2 + .../java/account/create-push-target.md | 2 + docs/examples/java/account/create-recovery.md | 2 + docs/examples/java/account/create-session.md | 2 + .../java/account/create-verification.md | 2 + docs/examples/java/account/create.md | 2 + .../java/account/delete-consent-token.md | 27 + docs/examples/java/account/delete-consent.md | 26 + docs/examples/java/account/delete-identity.md | 2 + .../java/account/delete-mfa-authenticator.md | 2 + .../java/account/delete-push-target.md | 2 + docs/examples/java/account/delete-session.md | 2 + docs/examples/java/account/delete-sessions.md | 2 + .../java/account/get-consent-token.md | 27 + docs/examples/java/account/get-consent.md | 26 + .../java/account/get-mfa-recovery-codes.md | 2 + docs/examples/java/account/get-prefs.md | 2 + docs/examples/java/account/get-session.md | 2 + docs/examples/java/account/get.md | 2 + .../java/account/list-consent-tokens.md | 28 + docs/examples/java/account/list-consents.md | 27 + docs/examples/java/account/list-identities.md | 2 + docs/examples/java/account/list-logs.md | 2 + .../examples/java/account/list-mfa-factors.md | 2 + docs/examples/java/account/list-sessions.md | 2 + .../java/account/update-email-verification.md | 2 + docs/examples/java/account/update-email.md | 2 + .../java/account/update-magic-url-session.md | 2 + .../java/account/update-mfa-authenticator.md | 2 + .../java/account/update-mfa-challenge.md | 2 + .../java/account/update-mfa-recovery-codes.md | 2 + docs/examples/java/account/update-mfa.md | 2 + docs/examples/java/account/update-name.md | 2 + docs/examples/java/account/update-password.md | 2 + .../java/account/update-phone-session.md | 2 + .../java/account/update-phone-verification.md | 2 + docs/examples/java/account/update-phone.md | 2 + docs/examples/java/account/update-prefs.md | 2 + .../java/account/update-push-target.md | 2 + docs/examples/java/account/update-recovery.md | 2 + docs/examples/java/account/update-session.md | 2 + docs/examples/java/account/update-status.md | 2 + .../java/account/update-verification.md | 2 + docs/examples/java/avatars/get-browser.md | 2 + docs/examples/java/avatars/get-credit-card.md | 2 + docs/examples/java/avatars/get-favicon.md | 2 + docs/examples/java/avatars/get-flag.md | 2 + docs/examples/java/avatars/get-image.md | 2 + docs/examples/java/avatars/get-initials.md | 2 + docs/examples/java/avatars/get-photo.md | 30 + docs/examples/java/avatars/get-qr.md | 2 + docs/examples/java/avatars/get-screenshot.md | 2 + .../java/databases/create-document.md | 2 + .../java/databases/create-operations.md | 2 + .../java/databases/create-transaction.md | 2 + .../databases/decrement-document-attribute.md | 2 + .../java/databases/delete-document.md | 2 + .../java/databases/delete-transaction.md | 2 + docs/examples/java/databases/get-document.md | 2 + .../java/databases/get-transaction.md | 2 + .../databases/increment-document-attribute.md | 2 + .../examples/java/databases/list-documents.md | 2 + .../java/databases/list-transactions.md | 2 + .../java/databases/update-document.md | 2 + .../java/databases/update-transaction.md | 2 + .../java/databases/upsert-document.md | 2 + .../java/functions/create-execution.md | 2 + docs/examples/java/functions/get-execution.md | 2 + .../java/functions/list-executions.md | 2 + docs/examples/java/graphql/mutation.md | 2 + docs/examples/java/graphql/query.md | 2 + docs/examples/java/locale/get.md | 2 + docs/examples/java/locale/list-codes.md | 2 + docs/examples/java/locale/list-continents.md | 2 + .../examples/java/locale/list-countries-eu.md | 2 + .../java/locale/list-countries-phones.md | 2 + docs/examples/java/locale/list-countries.md | 2 + docs/examples/java/locale/list-currencies.md | 2 + docs/examples/java/locale/list-languages.md | 2 + .../java/messaging/create-subscriber.md | 2 + .../java/messaging/delete-subscriber.md | 2 + .../java/organization/create-installation.md | 27 + .../java/organization/delete-installation.md | 26 + .../java/organization/get-installation.md | 26 + .../java/organization/list-installations.md | 27 + .../java/organization/update-installation.md | 27 + docs/examples/java/presences/delete.md | 2 + docs/examples/java/presences/get.md | 2 + docs/examples/java/presences/list.md | 2 + docs/examples/java/presences/update.md | 2 + docs/examples/java/presences/upsert.md | 2 + docs/examples/java/storage/create-file.md | 3 + docs/examples/java/storage/delete-file.md | 2 + .../java/storage/get-file-download.md | 2 + .../examples/java/storage/get-file-preview.md | 2 + docs/examples/java/storage/get-file-view.md | 2 + docs/examples/java/storage/get-file.md | 2 + docs/examples/java/storage/list-files.md | 2 + docs/examples/java/storage/update-file.md | 2 + .../java/tablesdb/create-operations.md | 2 + docs/examples/java/tablesdb/create-row.md | 2 + .../java/tablesdb/create-transaction.md | 2 + .../java/tablesdb/decrement-row-column.md | 2 + docs/examples/java/tablesdb/delete-row.md | 2 + .../java/tablesdb/delete-transaction.md | 2 + docs/examples/java/tablesdb/get-row.md | 2 + .../examples/java/tablesdb/get-transaction.md | 2 + .../java/tablesdb/increment-row-column.md | 2 + docs/examples/java/tablesdb/list-rows.md | 2 + .../java/tablesdb/list-transactions.md | 2 + docs/examples/java/tablesdb/update-row.md | 2 + .../java/tablesdb/update-transaction.md | 2 + docs/examples/java/tablesdb/upsert-row.md | 2 + .../java/teams/create-installation.md | 28 + docs/examples/java/teams/create-membership.md | 2 + docs/examples/java/teams/create.md | 2 + .../java/teams/delete-installation.md | 27 + docs/examples/java/teams/delete-membership.md | 2 + docs/examples/java/teams/delete.md | 2 + docs/examples/java/teams/get-installation.md | 27 + docs/examples/java/teams/get-membership.md | 2 + docs/examples/java/teams/get-prefs.md | 2 + docs/examples/java/teams/get.md | 2 + .../examples/java/teams/list-installations.md | 28 + docs/examples/java/teams/list-memberships.md | 2 + docs/examples/java/teams/list.md | 2 + .../java/teams/update-installation.md | 28 + .../java/teams/update-membership-status.md | 2 + docs/examples/java/teams/update-membership.md | 2 + docs/examples/java/teams/update-name.md | 2 + docs/examples/java/teams/update-prefs.md | 2 + .../kotlin/account/delete-consent-token.md | 16 + .../examples/kotlin/account/delete-consent.md | 15 + .../kotlin/account/get-consent-token.md | 16 + docs/examples/kotlin/account/get-consent.md | 15 + .../kotlin/account/list-consent-tokens.md | 17 + docs/examples/kotlin/account/list-consents.md | 16 + docs/examples/kotlin/avatars/get-photo.md | 19 + .../organization/create-installation.md | 16 + .../organization/delete-installation.md | 15 + .../kotlin/organization/get-installation.md | 15 + .../kotlin/organization/list-installations.md | 16 + .../organization/update-installation.md | 16 + docs/examples/kotlin/storage/create-file.md | 1 + .../kotlin/teams/create-installation.md | 17 + .../kotlin/teams/delete-installation.md | 16 + .../examples/kotlin/teams/get-installation.md | 16 + .../kotlin/teams/list-installations.md | 17 + .../kotlin/teams/update-installation.md | 17 + example/build.gradle.kts | 6 +- .../java/io/appwrite/android/MainActivity.kt | 4 +- .../android/services/MessagingService.kt | 2 +- .../android/ui/accounts/AccountsFragment.kt | 16 +- .../android/ui/accounts/AccountsViewModel.kt | 2 +- .../java/io/appwrite/android/utils/Client.kt | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- library/build.gradle.kts | 6 +- library/src/main/java/io/appwrite/Client.kt | 69 +- library/src/main/java/io/appwrite/ID.kt | 2 +- .../main/java/io/appwrite/KeepAliveService.kt | 6 +- .../src/main/java/io/appwrite/Permission.kt | 18 +- library/src/main/java/io/appwrite/Query.kt | 4 +- library/src/main/java/io/appwrite/Role.kt | 8 +- .../main/java/io/appwrite/WebAuthComponent.kt | 2 +- .../java/io/appwrite/cookies/Extensions.kt | 2 +- .../io/appwrite/cookies/InternalCookie.kt | 2 +- .../cookies/stores/InMemoryCookieStore.kt | 13 +- .../stores/SharedPreferencesCookieStore.kt | 4 +- .../java/io/appwrite/coroutines/Callback.kt | 2 +- .../io/appwrite/enums/AuthenticationFactor.kt | 6 +- .../io/appwrite/enums/AuthenticatorType.kt | 2 +- .../main/java/io/appwrite/enums/Browser.kt | 2 +- .../io/appwrite/enums/BrowserPermission.kt | 2 +- .../java/io/appwrite/enums/BrowserTheme.kt | 2 +- .../main/java/io/appwrite/enums/CreditCard.kt | 2 +- .../java/io/appwrite/enums/ExecutionMethod.kt | 2 +- .../appwrite/enums/ExecutionResourceType.kt | 12 + .../java/io/appwrite/enums/ExecutionStatus.kt | 2 +- .../io/appwrite/enums/ExecutionTrigger.kt | 2 +- .../src/main/java/io/appwrite/enums/Flag.kt | 2 +- .../java/io/appwrite/enums/ImageFormat.kt | 2 +- .../java/io/appwrite/enums/ImageGravity.kt | 2 +- .../java/io/appwrite/enums/OAuthProvider.kt | 4 +- .../main/java/io/appwrite/enums/Timezone.kt | 2 +- .../appwrite/exceptions/AppwriteException.kt | 2 +- .../extensions/CollectionExtensions.kt | 2 +- .../io/appwrite/extensions/JsonExtensions.kt | 21 + .../io/appwrite/extensions/TypeExtensions.kt | 2 +- .../java/io/appwrite/models/AlgoArgon2.kt | 1 - .../java/io/appwrite/models/AlgoBcrypt.kt | 1 - .../main/java/io/appwrite/models/AlgoMd5.kt | 1 - .../java/io/appwrite/models/AlgoPhpass.kt | 1 - .../java/io/appwrite/models/AlgoScrypt.kt | 1 - .../io/appwrite/models/AlgoScryptModified.kt | 1 - .../main/java/io/appwrite/models/AlgoSha.kt | 1 - .../io/appwrite/models/AppInstallation.kt | 101 +++ .../io/appwrite/models/AppInstallationList.kt | 37 ++ .../main/java/io/appwrite/models/Continent.kt | 1 - .../java/io/appwrite/models/ContinentList.kt | 1 - .../main/java/io/appwrite/models/Country.kt | 1 - .../java/io/appwrite/models/CountryList.kt | 1 - .../main/java/io/appwrite/models/Currency.kt | 1 - .../java/io/appwrite/models/CurrencyList.kt | 1 - .../main/java/io/appwrite/models/Execution.kt | 32 +- .../java/io/appwrite/models/ExecutionList.kt | 1 - .../src/main/java/io/appwrite/models/File.kt | 17 +- .../main/java/io/appwrite/models/FileList.kt | 1 - .../main/java/io/appwrite/models/Headers.kt | 1 - .../main/java/io/appwrite/models/Identity.kt | 1 - .../java/io/appwrite/models/IdentityList.kt | 1 - .../main/java/io/appwrite/models/InputFile.kt | 2 +- .../src/main/java/io/appwrite/models/Jwt.kt | 1 - .../main/java/io/appwrite/models/Language.kt | 1 - .../java/io/appwrite/models/LanguageList.kt | 1 - .../main/java/io/appwrite/models/Locale.kt | 1 - .../java/io/appwrite/models/LocaleCode.kt | 1 - .../java/io/appwrite/models/LocaleCodeList.kt | 1 - .../src/main/java/io/appwrite/models/Log.kt | 1 - .../main/java/io/appwrite/models/LogList.kt | 1 - .../java/io/appwrite/models/Membership.kt | 1 - .../java/io/appwrite/models/MembershipList.kt | 1 - .../java/io/appwrite/models/MfaChallenge.kt | 1 - .../java/io/appwrite/models/MfaFactors.kt | 9 +- .../io/appwrite/models/MfaRecoveryCodes.kt | 1 - .../main/java/io/appwrite/models/MfaType.kt | 1 - .../java/io/appwrite/models/Oauth2Consent.kt | 101 +++ .../io/appwrite/models/Oauth2ConsentList.kt | 37 ++ .../io/appwrite/models/Oauth2ConsentToken.kt | 109 +++ .../appwrite/models/Oauth2ConsentTokenList.kt | 37 ++ .../src/main/java/io/appwrite/models/Phone.kt | 1 - .../main/java/io/appwrite/models/PhoneList.kt | 1 - .../main/java/io/appwrite/models/Presence.kt | 5 +- .../java/io/appwrite/models/PresenceList.kt | 1 - .../java/io/appwrite/models/RealtimeModels.kt | 2 +- .../main/java/io/appwrite/models/Session.kt | 1 - .../java/io/appwrite/models/SessionList.kt | 1 - .../java/io/appwrite/models/Subscriber.kt | 1 - .../main/java/io/appwrite/models/Target.kt | 1 - .../src/main/java/io/appwrite/models/Token.kt | 1 - .../java/io/appwrite/models/Transaction.kt | 1 - .../io/appwrite/models/TransactionList.kt | 1 - .../java/io/appwrite/models/UploadProgress.kt | 2 +- .../src/main/java/io/appwrite/models/User.kt | 6 +- .../main/java/io/appwrite/services/Account.kt | 618 +++++++++--------- .../main/java/io/appwrite/services/Avatars.kt | 161 +++-- .../java/io/appwrite/services/Databases.kt | 54 +- .../java/io/appwrite/services/Functions.kt | 18 +- .../main/java/io/appwrite/services/Graphql.kt | 21 +- .../main/java/io/appwrite/services/Locale.kt | 97 +-- .../java/io/appwrite/services/Messaging.kt | 14 +- .../java/io/appwrite/services/Organization.kt | 180 +++++ .../java/io/appwrite/services/Presences.kt | 40 +- .../java/io/appwrite/services/Realtime.kt | 12 +- .../main/java/io/appwrite/services/Storage.kt | 65 +- .../java/io/appwrite/services/TablesDB.kt | 54 +- .../main/java/io/appwrite/services/Teams.kt | 245 +++++-- .../io/appwrite/views/CallbackActivity.kt | 4 +- .../java/io/appwrite/JsonRequestBodyTest.kt | 33 + 275 files changed, 2612 insertions(+), 845 deletions(-) create mode 100644 .editorconfig create mode 100644 docs/examples/java/account/delete-consent-token.md create mode 100644 docs/examples/java/account/delete-consent.md create mode 100644 docs/examples/java/account/get-consent-token.md create mode 100644 docs/examples/java/account/get-consent.md create mode 100644 docs/examples/java/account/list-consent-tokens.md create mode 100644 docs/examples/java/account/list-consents.md create mode 100644 docs/examples/java/avatars/get-photo.md create mode 100644 docs/examples/java/organization/create-installation.md create mode 100644 docs/examples/java/organization/delete-installation.md create mode 100644 docs/examples/java/organization/get-installation.md create mode 100644 docs/examples/java/organization/list-installations.md create mode 100644 docs/examples/java/organization/update-installation.md create mode 100644 docs/examples/java/teams/create-installation.md create mode 100644 docs/examples/java/teams/delete-installation.md create mode 100644 docs/examples/java/teams/get-installation.md create mode 100644 docs/examples/java/teams/list-installations.md create mode 100644 docs/examples/java/teams/update-installation.md create mode 100644 docs/examples/kotlin/account/delete-consent-token.md create mode 100644 docs/examples/kotlin/account/delete-consent.md create mode 100644 docs/examples/kotlin/account/get-consent-token.md create mode 100644 docs/examples/kotlin/account/get-consent.md create mode 100644 docs/examples/kotlin/account/list-consent-tokens.md create mode 100644 docs/examples/kotlin/account/list-consents.md create mode 100644 docs/examples/kotlin/avatars/get-photo.md create mode 100644 docs/examples/kotlin/organization/create-installation.md create mode 100644 docs/examples/kotlin/organization/delete-installation.md create mode 100644 docs/examples/kotlin/organization/get-installation.md create mode 100644 docs/examples/kotlin/organization/list-installations.md create mode 100644 docs/examples/kotlin/organization/update-installation.md create mode 100644 docs/examples/kotlin/teams/create-installation.md create mode 100644 docs/examples/kotlin/teams/delete-installation.md create mode 100644 docs/examples/kotlin/teams/get-installation.md create mode 100644 docs/examples/kotlin/teams/list-installations.md create mode 100644 docs/examples/kotlin/teams/update-installation.md create mode 100644 library/src/main/java/io/appwrite/enums/ExecutionResourceType.kt create mode 100644 library/src/main/java/io/appwrite/models/AppInstallation.kt create mode 100644 library/src/main/java/io/appwrite/models/AppInstallationList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Consent.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2ConsentToken.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt create mode 100644 library/src/main/java/io/appwrite/services/Organization.kt create mode 100644 library/src/test/java/io/appwrite/JsonRequestBodyTest.kt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..f0895a33 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +root = true + +[*.{kt,kts}] +ktlint_code_style = ktlint_official +ktlint_standard_annotation = disabled +ktlint_standard_argument-list-wrapping = disabled +ktlint_standard_blank-line-before-declaration = disabled +ktlint_standard_blank-line-between-when-conditions = disabled +ktlint_standard_chain-method-continuation = disabled +ktlint_standard_class-signature = disabled +ktlint_standard_function-expression-body = disabled +ktlint_standard_function-signature = disabled +ktlint_standard_function-start-of-body-spacing = disabled +ktlint_standard_if-else-bracing = disabled +ktlint_standard_if-else-wrapping = disabled +ktlint_standard_max-line-length = disabled +ktlint_standard_multiline-expression-wrapping = disabled +ktlint_standard_multiline-if-else = disabled +ktlint_standard_no-blank-line-before-rbrace = disabled +ktlint_standard_no-blank-line-in-list = disabled +ktlint_standard_no-empty-first-line-in-class-body = disabled +ktlint_standard_no-line-break-before-assignment = disabled +ktlint_standard_no-wildcard-imports = disabled +ktlint_standard_parameter-list-wrapping = disabled +ktlint_standard_spacing-between-declarations-with-annotations = disabled +ktlint_standard_spacing-between-declarations-with-comments = disabled +ktlint_standard_string-template = disabled +ktlint_standard_trailing-comma-on-call-site = disabled +ktlint_standard_trailing-comma-on-declaration-site = disabled +ktlint_standard_when-entry-bracing = disabled +ktlint_standard_wrapping = disabled + +[**/Channel.kt] +# Marker types intentionally use leading underscores to distinguish channel states. +ktlint_standard_class-naming = disabled + +[**/models/*.kt] +# Model imports follow schema property order so generated declarations stay deterministic. +ktlint_standard_import-ordering = disabled diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6230f5a8..2cba57ee 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK 17 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: temurin java-version: 17 diff --git a/CHANGELOG.md b/CHANGELOG.md index d73d8efe..700ff96c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 27.0.0 + +* Breaking: object parameters and model fields are now typed `Map` instead of `Any` +* Fixed: passing `null` in `data` now clears the attribute instead of being dropped from the request +* Added: `account.listConsents`, `getConsent`, `deleteConsent` for OAuth2 consent management +* Added: `account.listConsentTokens`, `getConsentToken`, `deleteConsentToken`, plus the `Oauth2ConsentToken` model +* Added: `organization` service +* Added: `teams.listInstallations`, `getInstallation`, `createInstallation`, `updateInstallation`, `deleteInstallation`, plus the `AppInstallation` model +* Added: `avatars.getPhoto` +* Added: `ExecutionResourceType` enum +* Added: `Client.prepareRequest()` for inspecting an encoded request + ## 26.0.0 * Breaking: raised `minSdk` from 21 to 23, dropping support for Android 5.0/5.1 (API 21-22) diff --git a/README.md b/README.md index 196f9c42..62e9d751 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Maven Central](https://img.shields.io/maven-central/v/io.appwrite/sdk-for-android.svg?color=green&style=flat-square) ![License](https://img.shields.io/github/license/appwrite/sdk-for-android.svg?style=flat-square) -![Version](https://img.shields.io/badge/api%20version-1.9.5-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-1.9.6-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) @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:26.0.0") +implementation("io.appwrite:sdk-for-android:27.0.0") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 26.0.0 + 27.0.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 615848a8..252363de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,8 +2,8 @@ import java.util.Properties plugins { id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.android.library") version "9.2.1" apply false - id("com.android.application") version "9.2.1" apply false + id("com.android.library") version "9.3.2" apply false + id("com.android.application") version "9.3.2" apply false } version = System.getenv("SDK_VERSION") ?: "0.0.0" diff --git a/docs/examples/java/account/create-anonymous-session.md b/docs/examples/java/account/create-anonymous-session.md index a48b8ea0..4d99edf8 100644 --- a/docs/examples/java/account/create-anonymous-session.md +++ b/docs/examples/java/account/create-anonymous-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-email-password-session.md b/docs/examples/java/account/create-email-password-session.md index be91e8ef..fa7d2523 100644 --- a/docs/examples/java/account/create-email-password-session.md +++ b/docs/examples/java/account/create-email-password-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-email-token.md b/docs/examples/java/account/create-email-token.md index d1acde23..b6517837 100644 --- a/docs/examples/java/account/create-email-token.md +++ b/docs/examples/java/account/create-email-token.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-email-verification.md b/docs/examples/java/account/create-email-verification.md index c7f559a8..c8099477 100644 --- a/docs/examples/java/account/create-email-verification.md +++ b/docs/examples/java/account/create-email-verification.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-jwt.md b/docs/examples/java/account/create-jwt.md index f05bbcaf..f405d306 100644 --- a/docs/examples/java/account/create-jwt.md +++ b/docs/examples/java/account/create-jwt.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-magic-url-token.md b/docs/examples/java/account/create-magic-url-token.md index 6a18e65c..e087ea80 100644 --- a/docs/examples/java/account/create-magic-url-token.md +++ b/docs/examples/java/account/create-magic-url-token.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-mfa-authenticator.md b/docs/examples/java/account/create-mfa-authenticator.md index 404e0420..a87f0101 100644 --- a/docs/examples/java/account/create-mfa-authenticator.md +++ b/docs/examples/java/account/create-mfa-authenticator.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-mfa-challenge.md b/docs/examples/java/account/create-mfa-challenge.md index 2cb46fdc..d0e9d5cb 100644 --- a/docs/examples/java/account/create-mfa-challenge.md +++ b/docs/examples/java/account/create-mfa-challenge.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-mfa-recovery-codes.md b/docs/examples/java/account/create-mfa-recovery-codes.md index e97714c6..caaa86aa 100644 --- a/docs/examples/java/account/create-mfa-recovery-codes.md +++ b/docs/examples/java/account/create-mfa-recovery-codes.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-o-auth-2-session.md b/docs/examples/java/account/create-o-auth-2-session.md index b34bcf76..da547421 100644 --- a/docs/examples/java/account/create-o-auth-2-session.md +++ b/docs/examples/java/account/create-o-auth-2-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-o-auth-2-token.md b/docs/examples/java/account/create-o-auth-2-token.md index a6966654..ae685850 100644 --- a/docs/examples/java/account/create-o-auth-2-token.md +++ b/docs/examples/java/account/create-o-auth-2-token.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-phone-token.md b/docs/examples/java/account/create-phone-token.md index 0808373d..a9568376 100644 --- a/docs/examples/java/account/create-phone-token.md +++ b/docs/examples/java/account/create-phone-token.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-phone-verification.md b/docs/examples/java/account/create-phone-verification.md index fa6bf74c..db54d3c8 100644 --- a/docs/examples/java/account/create-phone-verification.md +++ b/docs/examples/java/account/create-phone-verification.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-push-target.md b/docs/examples/java/account/create-push-target.md index 6842e5ba..34d903c4 100644 --- a/docs/examples/java/account/create-push-target.md +++ b/docs/examples/java/account/create-push-target.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-recovery.md b/docs/examples/java/account/create-recovery.md index 5647bac4..acc22302 100644 --- a/docs/examples/java/account/create-recovery.md +++ b/docs/examples/java/account/create-recovery.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-session.md b/docs/examples/java/account/create-session.md index e8bb63e2..e9ea8d70 100644 --- a/docs/examples/java/account/create-session.md +++ b/docs/examples/java/account/create-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create-verification.md b/docs/examples/java/account/create-verification.md index ab3d3939..3c80fbef 100644 --- a/docs/examples/java/account/create-verification.md +++ b/docs/examples/java/account/create-verification.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/create.md b/docs/examples/java/account/create.md index 9cf0a722..6a84b3c5 100644 --- a/docs/examples/java/account/create.md +++ b/docs/examples/java/account/create.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/delete-consent-token.md b/docs/examples/java/account/delete-consent-token.md new file mode 100644 index 00000000..de7cf32a --- /dev/null +++ b/docs/examples/java/account/delete-consent-token.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.deleteConsentToken( + "", // consentId + "", // tokenId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/delete-consent.md b/docs/examples/java/account/delete-consent.md new file mode 100644 index 00000000..84450cce --- /dev/null +++ b/docs/examples/java/account/delete-consent.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.deleteConsent( + "", // consentId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/delete-identity.md b/docs/examples/java/account/delete-identity.md index 807f11a3..513de636 100644 --- a/docs/examples/java/account/delete-identity.md +++ b/docs/examples/java/account/delete-identity.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/delete-mfa-authenticator.md b/docs/examples/java/account/delete-mfa-authenticator.md index d0409339..c57eeab8 100644 --- a/docs/examples/java/account/delete-mfa-authenticator.md +++ b/docs/examples/java/account/delete-mfa-authenticator.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/delete-push-target.md b/docs/examples/java/account/delete-push-target.md index 75afef20..a9e6aff9 100644 --- a/docs/examples/java/account/delete-push-target.md +++ b/docs/examples/java/account/delete-push-target.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/delete-session.md b/docs/examples/java/account/delete-session.md index 6536a7d7..84a73503 100644 --- a/docs/examples/java/account/delete-session.md +++ b/docs/examples/java/account/delete-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/delete-sessions.md b/docs/examples/java/account/delete-sessions.md index 391d701b..4b93d19a 100644 --- a/docs/examples/java/account/delete-sessions.md +++ b/docs/examples/java/account/delete-sessions.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/get-consent-token.md b/docs/examples/java/account/get-consent-token.md new file mode 100644 index 00000000..1395852f --- /dev/null +++ b/docs/examples/java/account/get-consent-token.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.getConsentToken( + "", // consentId + "", // tokenId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/get-consent.md b/docs/examples/java/account/get-consent.md new file mode 100644 index 00000000..54f2bf2d --- /dev/null +++ b/docs/examples/java/account/get-consent.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.getConsent( + "", // consentId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/get-mfa-recovery-codes.md b/docs/examples/java/account/get-mfa-recovery-codes.md index 5aa0df20..36814e6c 100644 --- a/docs/examples/java/account/get-mfa-recovery-codes.md +++ b/docs/examples/java/account/get-mfa-recovery-codes.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/get-prefs.md b/docs/examples/java/account/get-prefs.md index 8c08a847..f951fe9b 100644 --- a/docs/examples/java/account/get-prefs.md +++ b/docs/examples/java/account/get-prefs.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/get-session.md b/docs/examples/java/account/get-session.md index a499beae..1fb6f1ba 100644 --- a/docs/examples/java/account/get-session.md +++ b/docs/examples/java/account/get-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/get.md b/docs/examples/java/account/get.md index 6d649b99..e452e891 100644 --- a/docs/examples/java/account/get.md +++ b/docs/examples/java/account/get.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/list-consent-tokens.md b/docs/examples/java/account/list-consent-tokens.md new file mode 100644 index 00000000..0552b7dc --- /dev/null +++ b/docs/examples/java/account/list-consent-tokens.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.listConsentTokens( + "", // consentId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/list-consents.md b/docs/examples/java/account/list-consents.md new file mode 100644 index 00000000..5db215fe --- /dev/null +++ b/docs/examples/java/account/list-consents.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.listConsents( + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/list-identities.md b/docs/examples/java/account/list-identities.md index 78848d43..1222edaa 100644 --- a/docs/examples/java/account/list-identities.md +++ b/docs/examples/java/account/list-identities.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/list-logs.md b/docs/examples/java/account/list-logs.md index e4114920..e8de501d 100644 --- a/docs/examples/java/account/list-logs.md +++ b/docs/examples/java/account/list-logs.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/list-mfa-factors.md b/docs/examples/java/account/list-mfa-factors.md index 869701dd..f0c3e01a 100644 --- a/docs/examples/java/account/list-mfa-factors.md +++ b/docs/examples/java/account/list-mfa-factors.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/list-sessions.md b/docs/examples/java/account/list-sessions.md index 44b453f5..9e52ea47 100644 --- a/docs/examples/java/account/list-sessions.md +++ b/docs/examples/java/account/list-sessions.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-email-verification.md b/docs/examples/java/account/update-email-verification.md index f3563cfc..1c9bc865 100644 --- a/docs/examples/java/account/update-email-verification.md +++ b/docs/examples/java/account/update-email-verification.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-email.md b/docs/examples/java/account/update-email.md index d9a42148..d077f9f4 100644 --- a/docs/examples/java/account/update-email.md +++ b/docs/examples/java/account/update-email.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-magic-url-session.md b/docs/examples/java/account/update-magic-url-session.md index 666d0b01..be3e9b0c 100644 --- a/docs/examples/java/account/update-magic-url-session.md +++ b/docs/examples/java/account/update-magic-url-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-mfa-authenticator.md b/docs/examples/java/account/update-mfa-authenticator.md index 492a12bd..e2b017cf 100644 --- a/docs/examples/java/account/update-mfa-authenticator.md +++ b/docs/examples/java/account/update-mfa-authenticator.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-mfa-challenge.md b/docs/examples/java/account/update-mfa-challenge.md index 857009ad..132b2994 100644 --- a/docs/examples/java/account/update-mfa-challenge.md +++ b/docs/examples/java/account/update-mfa-challenge.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-mfa-recovery-codes.md b/docs/examples/java/account/update-mfa-recovery-codes.md index bf2818d5..38c2fa96 100644 --- a/docs/examples/java/account/update-mfa-recovery-codes.md +++ b/docs/examples/java/account/update-mfa-recovery-codes.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-mfa.md b/docs/examples/java/account/update-mfa.md index c1937c0c..dc9a3d76 100644 --- a/docs/examples/java/account/update-mfa.md +++ b/docs/examples/java/account/update-mfa.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-name.md b/docs/examples/java/account/update-name.md index a64dd6ed..3aca6d88 100644 --- a/docs/examples/java/account/update-name.md +++ b/docs/examples/java/account/update-name.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-password.md b/docs/examples/java/account/update-password.md index 9d07bdbb..c491f4b9 100644 --- a/docs/examples/java/account/update-password.md +++ b/docs/examples/java/account/update-password.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-phone-session.md b/docs/examples/java/account/update-phone-session.md index d15a5c49..edaa99d4 100644 --- a/docs/examples/java/account/update-phone-session.md +++ b/docs/examples/java/account/update-phone-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-phone-verification.md b/docs/examples/java/account/update-phone-verification.md index 8f1b4153..b8f19abd 100644 --- a/docs/examples/java/account/update-phone-verification.md +++ b/docs/examples/java/account/update-phone-verification.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-phone.md b/docs/examples/java/account/update-phone.md index da17d4b8..6613850b 100644 --- a/docs/examples/java/account/update-phone.md +++ b/docs/examples/java/account/update-phone.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-prefs.md b/docs/examples/java/account/update-prefs.md index 1996e605..819a8996 100644 --- a/docs/examples/java/account/update-prefs.md +++ b/docs/examples/java/account/update-prefs.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-push-target.md b/docs/examples/java/account/update-push-target.md index 67acc1bc..19778785 100644 --- a/docs/examples/java/account/update-push-target.md +++ b/docs/examples/java/account/update-push-target.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-recovery.md b/docs/examples/java/account/update-recovery.md index ce60d60a..96741d0a 100644 --- a/docs/examples/java/account/update-recovery.md +++ b/docs/examples/java/account/update-recovery.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-session.md b/docs/examples/java/account/update-session.md index 978d7a44..bf54de4d 100644 --- a/docs/examples/java/account/update-session.md +++ b/docs/examples/java/account/update-session.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-status.md b/docs/examples/java/account/update-status.md index 308df116..a0537aeb 100644 --- a/docs/examples/java/account/update-status.md +++ b/docs/examples/java/account/update-status.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/account/update-verification.md b/docs/examples/java/account/update-verification.md index e1819262..2f63c5bd 100644 --- a/docs/examples/java/account/update-verification.md +++ b/docs/examples/java/account/update-verification.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Account; diff --git a/docs/examples/java/avatars/get-browser.md b/docs/examples/java/avatars/get-browser.md index f107aee6..f101ad54 100644 --- a/docs/examples/java/avatars/get-browser.md +++ b/docs/examples/java/avatars/get-browser.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/avatars/get-credit-card.md b/docs/examples/java/avatars/get-credit-card.md index 40fb5660..b12cebc9 100644 --- a/docs/examples/java/avatars/get-credit-card.md +++ b/docs/examples/java/avatars/get-credit-card.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/avatars/get-favicon.md b/docs/examples/java/avatars/get-favicon.md index 3427b59d..c7bc6411 100644 --- a/docs/examples/java/avatars/get-favicon.md +++ b/docs/examples/java/avatars/get-favicon.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/avatars/get-flag.md b/docs/examples/java/avatars/get-flag.md index 812282ff..4d808513 100644 --- a/docs/examples/java/avatars/get-flag.md +++ b/docs/examples/java/avatars/get-flag.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/avatars/get-image.md b/docs/examples/java/avatars/get-image.md index 1289ec1f..a22041f5 100644 --- a/docs/examples/java/avatars/get-image.md +++ b/docs/examples/java/avatars/get-image.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/avatars/get-initials.md b/docs/examples/java/avatars/get-initials.md index 419ca5e5..1b781b67 100644 --- a/docs/examples/java/avatars/get-initials.md +++ b/docs/examples/java/avatars/get-initials.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/avatars/get-photo.md b/docs/examples/java/avatars/get-photo.md new file mode 100644 index 00000000..b1d14dca --- /dev/null +++ b/docs/examples/java/avatars/get-photo.md @@ -0,0 +1,30 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Avatars; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Avatars avatars = new Avatars(client); + +avatars.getPhoto( + 0, // width (optional) + 0, // height (optional) + 0, // quality (optional) + "png", // output (optional) + "g", // rating (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/avatars/get-qr.md b/docs/examples/java/avatars/get-qr.md index be4d11c3..ad08ab56 100644 --- a/docs/examples/java/avatars/get-qr.md +++ b/docs/examples/java/avatars/get-qr.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/avatars/get-screenshot.md b/docs/examples/java/avatars/get-screenshot.md index c016ea5f..71f96c5b 100644 --- a/docs/examples/java/avatars/get-screenshot.md +++ b/docs/examples/java/avatars/get-screenshot.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Avatars; diff --git a/docs/examples/java/databases/create-document.md b/docs/examples/java/databases/create-document.md index 44ff1d27..3c64659d 100644 --- a/docs/examples/java/databases/create-document.md +++ b/docs/examples/java/databases/create-document.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/databases/create-operations.md b/docs/examples/java/databases/create-operations.md index e7e0a7f5..bbef8573 100644 --- a/docs/examples/java/databases/create-operations.md +++ b/docs/examples/java/databases/create-operations.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/create-transaction.md b/docs/examples/java/databases/create-transaction.md index 253e9650..d96c27a0 100644 --- a/docs/examples/java/databases/create-transaction.md +++ b/docs/examples/java/databases/create-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/decrement-document-attribute.md b/docs/examples/java/databases/decrement-document-attribute.md index 2c881a6a..f069abbf 100644 --- a/docs/examples/java/databases/decrement-document-attribute.md +++ b/docs/examples/java/databases/decrement-document-attribute.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/delete-document.md b/docs/examples/java/databases/delete-document.md index a1a0e11b..ef04ee69 100644 --- a/docs/examples/java/databases/delete-document.md +++ b/docs/examples/java/databases/delete-document.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/delete-transaction.md b/docs/examples/java/databases/delete-transaction.md index e6f8ee20..4a2d8f01 100644 --- a/docs/examples/java/databases/delete-transaction.md +++ b/docs/examples/java/databases/delete-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/get-document.md b/docs/examples/java/databases/get-document.md index cb3d6bb9..106afa2c 100644 --- a/docs/examples/java/databases/get-document.md +++ b/docs/examples/java/databases/get-document.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/get-transaction.md b/docs/examples/java/databases/get-transaction.md index 812dcc21..b33a4bc8 100644 --- a/docs/examples/java/databases/get-transaction.md +++ b/docs/examples/java/databases/get-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/increment-document-attribute.md b/docs/examples/java/databases/increment-document-attribute.md index 38ab4fa3..c41a9071 100644 --- a/docs/examples/java/databases/increment-document-attribute.md +++ b/docs/examples/java/databases/increment-document-attribute.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/list-documents.md b/docs/examples/java/databases/list-documents.md index 7edbec36..8569cb60 100644 --- a/docs/examples/java/databases/list-documents.md +++ b/docs/examples/java/databases/list-documents.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/list-transactions.md b/docs/examples/java/databases/list-transactions.md index 73bdb050..ad11c7f7 100644 --- a/docs/examples/java/databases/list-transactions.md +++ b/docs/examples/java/databases/list-transactions.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/update-document.md b/docs/examples/java/databases/update-document.md index fc9dae29..40dc7c7b 100644 --- a/docs/examples/java/databases/update-document.md +++ b/docs/examples/java/databases/update-document.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/databases/update-transaction.md b/docs/examples/java/databases/update-transaction.md index 6260ebc2..dfc4db62 100644 --- a/docs/examples/java/databases/update-transaction.md +++ b/docs/examples/java/databases/update-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Databases; diff --git a/docs/examples/java/databases/upsert-document.md b/docs/examples/java/databases/upsert-document.md index f69223cf..f5976a92 100644 --- a/docs/examples/java/databases/upsert-document.md +++ b/docs/examples/java/databases/upsert-document.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/functions/create-execution.md b/docs/examples/java/functions/create-execution.md index 14e1a5c4..1984020b 100644 --- a/docs/examples/java/functions/create-execution.md +++ b/docs/examples/java/functions/create-execution.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; diff --git a/docs/examples/java/functions/get-execution.md b/docs/examples/java/functions/get-execution.md index 276f048c..206b407c 100644 --- a/docs/examples/java/functions/get-execution.md +++ b/docs/examples/java/functions/get-execution.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; diff --git a/docs/examples/java/functions/list-executions.md b/docs/examples/java/functions/list-executions.md index d1244a51..3cd974de 100644 --- a/docs/examples/java/functions/list-executions.md +++ b/docs/examples/java/functions/list-executions.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; diff --git a/docs/examples/java/graphql/mutation.md b/docs/examples/java/graphql/mutation.md index 0e932f14..9cc7febe 100644 --- a/docs/examples/java/graphql/mutation.md +++ b/docs/examples/java/graphql/mutation.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Graphql; diff --git a/docs/examples/java/graphql/query.md b/docs/examples/java/graphql/query.md index 17d19ee1..25704b00 100644 --- a/docs/examples/java/graphql/query.md +++ b/docs/examples/java/graphql/query.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Graphql; diff --git a/docs/examples/java/locale/get.md b/docs/examples/java/locale/get.md index 2416447b..d2c4e4d2 100644 --- a/docs/examples/java/locale/get.md +++ b/docs/examples/java/locale/get.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/locale/list-codes.md b/docs/examples/java/locale/list-codes.md index c27f9ef2..534b1199 100644 --- a/docs/examples/java/locale/list-codes.md +++ b/docs/examples/java/locale/list-codes.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/locale/list-continents.md b/docs/examples/java/locale/list-continents.md index 64debed3..998a31be 100644 --- a/docs/examples/java/locale/list-continents.md +++ b/docs/examples/java/locale/list-continents.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/locale/list-countries-eu.md b/docs/examples/java/locale/list-countries-eu.md index 768cc906..5950c990 100644 --- a/docs/examples/java/locale/list-countries-eu.md +++ b/docs/examples/java/locale/list-countries-eu.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/locale/list-countries-phones.md b/docs/examples/java/locale/list-countries-phones.md index 90622f1a..6096758e 100644 --- a/docs/examples/java/locale/list-countries-phones.md +++ b/docs/examples/java/locale/list-countries-phones.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/locale/list-countries.md b/docs/examples/java/locale/list-countries.md index 0ba92127..140fc48a 100644 --- a/docs/examples/java/locale/list-countries.md +++ b/docs/examples/java/locale/list-countries.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/locale/list-currencies.md b/docs/examples/java/locale/list-currencies.md index 10b53124..0e5273d9 100644 --- a/docs/examples/java/locale/list-currencies.md +++ b/docs/examples/java/locale/list-currencies.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/locale/list-languages.md b/docs/examples/java/locale/list-languages.md index 35687ab6..e85bd0a1 100644 --- a/docs/examples/java/locale/list-languages.md +++ b/docs/examples/java/locale/list-languages.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Locale; diff --git a/docs/examples/java/messaging/create-subscriber.md b/docs/examples/java/messaging/create-subscriber.md index be1bfc57..88f9260d 100644 --- a/docs/examples/java/messaging/create-subscriber.md +++ b/docs/examples/java/messaging/create-subscriber.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; diff --git a/docs/examples/java/messaging/delete-subscriber.md b/docs/examples/java/messaging/delete-subscriber.md index c8c089f4..1c012a3f 100644 --- a/docs/examples/java/messaging/delete-subscriber.md +++ b/docs/examples/java/messaging/delete-subscriber.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Messaging; diff --git a/docs/examples/java/organization/create-installation.md b/docs/examples/java/organization/create-installation.md new file mode 100644 index 00000000..d4c40fbe --- /dev/null +++ b/docs/examples/java/organization/create-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Organization; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Organization organization = new Organization(client); + +organization.createInstallation( + "", // appId + "", // authorizationDetails (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/organization/delete-installation.md b/docs/examples/java/organization/delete-installation.md new file mode 100644 index 00000000..f1bc8718 --- /dev/null +++ b/docs/examples/java/organization/delete-installation.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Organization; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Organization organization = new Organization(client); + +organization.deleteInstallation( + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/organization/get-installation.md b/docs/examples/java/organization/get-installation.md new file mode 100644 index 00000000..20c8779f --- /dev/null +++ b/docs/examples/java/organization/get-installation.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Organization; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Organization organization = new Organization(client); + +organization.getInstallation( + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/organization/list-installations.md b/docs/examples/java/organization/list-installations.md new file mode 100644 index 00000000..6fa2c2a9 --- /dev/null +++ b/docs/examples/java/organization/list-installations.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Organization; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Organization organization = new Organization(client); + +organization.listInstallations( + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/organization/update-installation.md b/docs/examples/java/organization/update-installation.md new file mode 100644 index 00000000..f27fb55c --- /dev/null +++ b/docs/examples/java/organization/update-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Organization; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Organization organization = new Organization(client); + +organization.updateInstallation( + "", // installationId + "", // authorizationDetails (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/presences/delete.md b/docs/examples/java/presences/delete.md index 04dace36..f0f6afa2 100644 --- a/docs/examples/java/presences/delete.md +++ b/docs/examples/java/presences/delete.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Presences; diff --git a/docs/examples/java/presences/get.md b/docs/examples/java/presences/get.md index 8417a566..2d2d2cea 100644 --- a/docs/examples/java/presences/get.md +++ b/docs/examples/java/presences/get.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Presences; diff --git a/docs/examples/java/presences/list.md b/docs/examples/java/presences/list.md index e23f49a2..d66a4161 100644 --- a/docs/examples/java/presences/list.md +++ b/docs/examples/java/presences/list.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Presences; diff --git a/docs/examples/java/presences/update.md b/docs/examples/java/presences/update.md index b38a10f0..964f3a03 100644 --- a/docs/examples/java/presences/update.md +++ b/docs/examples/java/presences/update.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/presences/upsert.md b/docs/examples/java/presences/upsert.md index 3e1ef96b..2d279b3d 100644 --- a/docs/examples/java/presences/upsert.md +++ b/docs/examples/java/presences/upsert.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/storage/create-file.md b/docs/examples/java/storage/create-file.md index 3b73523b..bcc8296b 100644 --- a/docs/examples/java/storage/create-file.md +++ b/docs/examples/java/storage/create-file.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.models.InputFile; @@ -17,6 +19,7 @@ storage.createFile( "", // fileId InputFile.fromPath("file.png"), // file List.of(Permission.read(Role.any())), // permissions (optional) + "", // folder (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/storage/delete-file.md b/docs/examples/java/storage/delete-file.md index fdd9e8ba..67212a70 100644 --- a/docs/examples/java/storage/delete-file.md +++ b/docs/examples/java/storage/delete-file.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Storage; diff --git a/docs/examples/java/storage/get-file-download.md b/docs/examples/java/storage/get-file-download.md index 91d91b7d..88e24ca7 100644 --- a/docs/examples/java/storage/get-file-download.md +++ b/docs/examples/java/storage/get-file-download.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Storage; diff --git a/docs/examples/java/storage/get-file-preview.md b/docs/examples/java/storage/get-file-preview.md index 9355dc68..f7282076 100644 --- a/docs/examples/java/storage/get-file-preview.md +++ b/docs/examples/java/storage/get-file-preview.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Storage; diff --git a/docs/examples/java/storage/get-file-view.md b/docs/examples/java/storage/get-file-view.md index 66762c2b..c9d11938 100644 --- a/docs/examples/java/storage/get-file-view.md +++ b/docs/examples/java/storage/get-file-view.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Storage; diff --git a/docs/examples/java/storage/get-file.md b/docs/examples/java/storage/get-file.md index 17362657..1c5cb7af 100644 --- a/docs/examples/java/storage/get-file.md +++ b/docs/examples/java/storage/get-file.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Storage; diff --git a/docs/examples/java/storage/list-files.md b/docs/examples/java/storage/list-files.md index 3b4288ff..b6737bf0 100644 --- a/docs/examples/java/storage/list-files.md +++ b/docs/examples/java/storage/list-files.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Storage; diff --git a/docs/examples/java/storage/update-file.md b/docs/examples/java/storage/update-file.md index a8ff797d..225eaaac 100644 --- a/docs/examples/java/storage/update-file.md +++ b/docs/examples/java/storage/update-file.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/tablesdb/create-operations.md b/docs/examples/java/tablesdb/create-operations.md index 8ab0f41f..86cdfa3a 100644 --- a/docs/examples/java/tablesdb/create-operations.md +++ b/docs/examples/java/tablesdb/create-operations.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/create-row.md b/docs/examples/java/tablesdb/create-row.md index 772a4e07..4244a249 100644 --- a/docs/examples/java/tablesdb/create-row.md +++ b/docs/examples/java/tablesdb/create-row.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/tablesdb/create-transaction.md b/docs/examples/java/tablesdb/create-transaction.md index abf0aba1..8401bfec 100644 --- a/docs/examples/java/tablesdb/create-transaction.md +++ b/docs/examples/java/tablesdb/create-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/decrement-row-column.md b/docs/examples/java/tablesdb/decrement-row-column.md index 50225726..5f9b7ce1 100644 --- a/docs/examples/java/tablesdb/decrement-row-column.md +++ b/docs/examples/java/tablesdb/decrement-row-column.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/delete-row.md b/docs/examples/java/tablesdb/delete-row.md index 146f97f9..d00cf595 100644 --- a/docs/examples/java/tablesdb/delete-row.md +++ b/docs/examples/java/tablesdb/delete-row.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/delete-transaction.md b/docs/examples/java/tablesdb/delete-transaction.md index aa649862..f48b50ac 100644 --- a/docs/examples/java/tablesdb/delete-transaction.md +++ b/docs/examples/java/tablesdb/delete-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/get-row.md b/docs/examples/java/tablesdb/get-row.md index df07780e..1eae5257 100644 --- a/docs/examples/java/tablesdb/get-row.md +++ b/docs/examples/java/tablesdb/get-row.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/get-transaction.md b/docs/examples/java/tablesdb/get-transaction.md index 086c9f17..861aacb8 100644 --- a/docs/examples/java/tablesdb/get-transaction.md +++ b/docs/examples/java/tablesdb/get-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/increment-row-column.md b/docs/examples/java/tablesdb/increment-row-column.md index 1cad7857..ae97cdb8 100644 --- a/docs/examples/java/tablesdb/increment-row-column.md +++ b/docs/examples/java/tablesdb/increment-row-column.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/list-rows.md b/docs/examples/java/tablesdb/list-rows.md index 6ab5489d..cbf40287 100644 --- a/docs/examples/java/tablesdb/list-rows.md +++ b/docs/examples/java/tablesdb/list-rows.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/list-transactions.md b/docs/examples/java/tablesdb/list-transactions.md index 81c41237..9ebb180a 100644 --- a/docs/examples/java/tablesdb/list-transactions.md +++ b/docs/examples/java/tablesdb/list-transactions.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/update-row.md b/docs/examples/java/tablesdb/update-row.md index 96cb1b9e..bc3f84c1 100644 --- a/docs/examples/java/tablesdb/update-row.md +++ b/docs/examples/java/tablesdb/update-row.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/tablesdb/update-transaction.md b/docs/examples/java/tablesdb/update-transaction.md index ef6783e0..8f362d03 100644 --- a/docs/examples/java/tablesdb/update-transaction.md +++ b/docs/examples/java/tablesdb/update-transaction.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.TablesDB; diff --git a/docs/examples/java/tablesdb/upsert-row.md b/docs/examples/java/tablesdb/upsert-row.md index e4d7228e..4df4792a 100644 --- a/docs/examples/java/tablesdb/upsert-row.md +++ b/docs/examples/java/tablesdb/upsert-row.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.Permission; diff --git a/docs/examples/java/teams/create-installation.md b/docs/examples/java/teams/create-installation.md new file mode 100644 index 00000000..699f71b4 --- /dev/null +++ b/docs/examples/java/teams/create-installation.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Teams; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Teams teams = new Teams(client); + +teams.createInstallation( + "", // teamId + "", // appId + "", // authorizationDetails (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/teams/create-membership.md b/docs/examples/java/teams/create-membership.md index 8ec9e0fa..861aeb62 100644 --- a/docs/examples/java/teams/create-membership.md +++ b/docs/examples/java/teams/create-membership.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/create.md b/docs/examples/java/teams/create.md index b31554ce..e9a68004 100644 --- a/docs/examples/java/teams/create.md +++ b/docs/examples/java/teams/create.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/delete-installation.md b/docs/examples/java/teams/delete-installation.md new file mode 100644 index 00000000..da5d48fd --- /dev/null +++ b/docs/examples/java/teams/delete-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Teams; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Teams teams = new Teams(client); + +teams.deleteInstallation( + "", // teamId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/teams/delete-membership.md b/docs/examples/java/teams/delete-membership.md index 8680f127..3a0482db 100644 --- a/docs/examples/java/teams/delete-membership.md +++ b/docs/examples/java/teams/delete-membership.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/delete.md b/docs/examples/java/teams/delete.md index 2055157f..06f18673 100644 --- a/docs/examples/java/teams/delete.md +++ b/docs/examples/java/teams/delete.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/get-installation.md b/docs/examples/java/teams/get-installation.md new file mode 100644 index 00000000..9222d762 --- /dev/null +++ b/docs/examples/java/teams/get-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Teams; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Teams teams = new Teams(client); + +teams.getInstallation( + "", // teamId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/teams/get-membership.md b/docs/examples/java/teams/get-membership.md index 04f90637..50184960 100644 --- a/docs/examples/java/teams/get-membership.md +++ b/docs/examples/java/teams/get-membership.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/get-prefs.md b/docs/examples/java/teams/get-prefs.md index ff7dc426..c82fce98 100644 --- a/docs/examples/java/teams/get-prefs.md +++ b/docs/examples/java/teams/get-prefs.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/get.md b/docs/examples/java/teams/get.md index cfa9242e..df348f12 100644 --- a/docs/examples/java/teams/get.md +++ b/docs/examples/java/teams/get.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/list-installations.md b/docs/examples/java/teams/list-installations.md new file mode 100644 index 00000000..8aeace5f --- /dev/null +++ b/docs/examples/java/teams/list-installations.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Teams; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Teams teams = new Teams(client); + +teams.listInstallations( + "", // teamId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/teams/list-memberships.md b/docs/examples/java/teams/list-memberships.md index ed9b2744..79aad6de 100644 --- a/docs/examples/java/teams/list-memberships.md +++ b/docs/examples/java/teams/list-memberships.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/list.md b/docs/examples/java/teams/list.md index 07acaf8d..5d258284 100644 --- a/docs/examples/java/teams/list.md +++ b/docs/examples/java/teams/list.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/update-installation.md b/docs/examples/java/teams/update-installation.md new file mode 100644 index 00000000..cd32a5cd --- /dev/null +++ b/docs/examples/java/teams/update-installation.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Teams; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Teams teams = new Teams(client); + +teams.updateInstallation( + "", // teamId + "", // installationId + "", // authorizationDetails (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/teams/update-membership-status.md b/docs/examples/java/teams/update-membership-status.md index bcb46d81..a6a54df0 100644 --- a/docs/examples/java/teams/update-membership-status.md +++ b/docs/examples/java/teams/update-membership-status.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/update-membership.md b/docs/examples/java/teams/update-membership.md index c2a0b980..60231f2c 100644 --- a/docs/examples/java/teams/update-membership.md +++ b/docs/examples/java/teams/update-membership.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/update-name.md b/docs/examples/java/teams/update-name.md index 6a49b27c..1e104100 100644 --- a/docs/examples/java/teams/update-name.md +++ b/docs/examples/java/teams/update-name.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/java/teams/update-prefs.md b/docs/examples/java/teams/update-prefs.md index bedb83b3..69ed22ae 100644 --- a/docs/examples/java/teams/update-prefs.md +++ b/docs/examples/java/teams/update-prefs.md @@ -1,4 +1,6 @@ ```java +import android.util.Log; + import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Teams; diff --git a/docs/examples/kotlin/account/delete-consent-token.md b/docs/examples/kotlin/account/delete-consent-token.md new file mode 100644 index 00000000..82e2d535 --- /dev/null +++ b/docs/examples/kotlin/account/delete-consent-token.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.deleteConsentToken( + consentId = "", + tokenId = "", +) +``` diff --git a/docs/examples/kotlin/account/delete-consent.md b/docs/examples/kotlin/account/delete-consent.md new file mode 100644 index 00000000..278342a8 --- /dev/null +++ b/docs/examples/kotlin/account/delete-consent.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.deleteConsent( + consentId = "", +) +``` diff --git a/docs/examples/kotlin/account/get-consent-token.md b/docs/examples/kotlin/account/get-consent-token.md new file mode 100644 index 00000000..3109987d --- /dev/null +++ b/docs/examples/kotlin/account/get-consent-token.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.getConsentToken( + consentId = "", + tokenId = "", +) +``` diff --git a/docs/examples/kotlin/account/get-consent.md b/docs/examples/kotlin/account/get-consent.md new file mode 100644 index 00000000..be31d5f9 --- /dev/null +++ b/docs/examples/kotlin/account/get-consent.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.getConsent( + consentId = "", +) +``` diff --git a/docs/examples/kotlin/account/list-consent-tokens.md b/docs/examples/kotlin/account/list-consent-tokens.md new file mode 100644 index 00000000..8fab57d1 --- /dev/null +++ b/docs/examples/kotlin/account/list-consent-tokens.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.listConsentTokens( + consentId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/account/list-consents.md b/docs/examples/kotlin/account/list-consents.md new file mode 100644 index 00000000..fe392819 --- /dev/null +++ b/docs/examples/kotlin/account/list-consents.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.listConsents( + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/avatars/get-photo.md b/docs/examples/kotlin/avatars/get-photo.md new file mode 100644 index 00000000..a05805fb --- /dev/null +++ b/docs/examples/kotlin/avatars/get-photo.md @@ -0,0 +1,19 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val avatars = Avatars(client) + +val result = avatars.getPhoto( + width = 0, // (optional) + height = 0, // (optional) + quality = 0, // (optional) + output = "png", // (optional) + rating = "g", // (optional) +) +``` diff --git a/docs/examples/kotlin/organization/create-installation.md b/docs/examples/kotlin/organization/create-installation.md new file mode 100644 index 00000000..f5f79b42 --- /dev/null +++ b/docs/examples/kotlin/organization/create-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Organization + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val organization = Organization(client) + +val result = organization.createInstallation( + appId = "", + authorizationDetails = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/organization/delete-installation.md b/docs/examples/kotlin/organization/delete-installation.md new file mode 100644 index 00000000..c0f1b11d --- /dev/null +++ b/docs/examples/kotlin/organization/delete-installation.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Organization + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val organization = Organization(client) + +val result = organization.deleteInstallation( + installationId = "", +) +``` diff --git a/docs/examples/kotlin/organization/get-installation.md b/docs/examples/kotlin/organization/get-installation.md new file mode 100644 index 00000000..8a512807 --- /dev/null +++ b/docs/examples/kotlin/organization/get-installation.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Organization + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val organization = Organization(client) + +val result = organization.getInstallation( + installationId = "", +) +``` diff --git a/docs/examples/kotlin/organization/list-installations.md b/docs/examples/kotlin/organization/list-installations.md new file mode 100644 index 00000000..3f6d8390 --- /dev/null +++ b/docs/examples/kotlin/organization/list-installations.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Organization + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val organization = Organization(client) + +val result = organization.listInstallations( + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/organization/update-installation.md b/docs/examples/kotlin/organization/update-installation.md new file mode 100644 index 00000000..b5dbb792 --- /dev/null +++ b/docs/examples/kotlin/organization/update-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Organization + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val organization = Organization(client) + +val result = organization.updateInstallation( + installationId = "", + authorizationDetails = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/storage/create-file.md b/docs/examples/kotlin/storage/create-file.md index df33a5a2..44d3ac8b 100644 --- a/docs/examples/kotlin/storage/create-file.md +++ b/docs/examples/kotlin/storage/create-file.md @@ -17,5 +17,6 @@ val result = storage.createFile( fileId = "", file = InputFile.fromPath("file.png"), permissions = listOf(Permission.read(Role.any())), // (optional) + folder = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/teams/create-installation.md b/docs/examples/kotlin/teams/create-installation.md new file mode 100644 index 00000000..975cab99 --- /dev/null +++ b/docs/examples/kotlin/teams/create-installation.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val teams = Teams(client) + +val result = teams.createInstallation( + teamId = "", + appId = "", + authorizationDetails = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/teams/delete-installation.md b/docs/examples/kotlin/teams/delete-installation.md new file mode 100644 index 00000000..7db6a2c5 --- /dev/null +++ b/docs/examples/kotlin/teams/delete-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val teams = Teams(client) + +val result = teams.deleteInstallation( + teamId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/teams/get-installation.md b/docs/examples/kotlin/teams/get-installation.md new file mode 100644 index 00000000..805e97b3 --- /dev/null +++ b/docs/examples/kotlin/teams/get-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val teams = Teams(client) + +val result = teams.getInstallation( + teamId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/teams/list-installations.md b/docs/examples/kotlin/teams/list-installations.md new file mode 100644 index 00000000..6143b5b1 --- /dev/null +++ b/docs/examples/kotlin/teams/list-installations.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val teams = Teams(client) + +val result = teams.listInstallations( + teamId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/teams/update-installation.md b/docs/examples/kotlin/teams/update-installation.md new file mode 100644 index 00000000..e80ff3fd --- /dev/null +++ b/docs/examples/kotlin/teams/update-installation.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Teams + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val teams = Teams(client) + +val result = teams.updateInstallation( + teamId = "", + installationId = "", + authorizationDetails = "", // (optional) +) +``` diff --git a/example/build.gradle.kts b/example/build.gradle.kts index d2172f2c..2a7b4801 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -38,11 +38,11 @@ dependencies { implementation(project(":library")) implementation("androidx.core:core-ktx:1.19.0") - implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.appcompat:appcompat:1.8.0") implementation("com.google.android.material:material:1.14.0") - implementation("androidx.constraintlayout:constraintlayout:2.2.1") + implementation("androidx.constraintlayout:constraintlayout:2.2.2") implementation("androidx.navigation:navigation-fragment-ktx:2.9.8") - implementation("androidx.fragment:fragment-ktx:1.8.9") + implementation("androidx.fragment:fragment-ktx:1.9.0") implementation("androidx.navigation:navigation-ui-ktx:2.9.8") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.11.0") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0") diff --git a/example/src/main/java/io/appwrite/android/MainActivity.kt b/example/src/main/java/io/appwrite/android/MainActivity.kt index 2fe5ae9c..28474ed8 100644 --- a/example/src/main/java/io/appwrite/android/MainActivity.kt +++ b/example/src/main/java/io/appwrite/android/MainActivity.kt @@ -1,7 +1,7 @@ package io.appwrite.android -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.add import androidx.fragment.app.commit import io.appwrite.android.ui.accounts.AccountsFragment @@ -20,4 +20,4 @@ class MainActivity : AppCompatActivity() { } } } -} \ No newline at end of file +} diff --git a/example/src/main/java/io/appwrite/android/services/MessagingService.kt b/example/src/main/java/io/appwrite/android/services/MessagingService.kt index 8470c66c..935a6853 100644 --- a/example/src/main/java/io/appwrite/android/services/MessagingService.kt +++ b/example/src/main/java/io/appwrite/android/services/MessagingService.kt @@ -34,4 +34,4 @@ class MessagingService : FirebaseMessagingService() { } } } -} \ No newline at end of file +} diff --git a/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt b/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt index 4e608084..92903fdf 100644 --- a/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt +++ b/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt @@ -19,8 +19,8 @@ class AccountsFragment : Fragment() { private val viewModel: AccountsViewModel by viewModels() override fun onCreateView( - inflater: LayoutInflater , - container: ViewGroup? , + inflater: LayoutInflater, + container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = DataBindingUtil.inflate( @@ -31,7 +31,7 @@ class AccountsFragment : Fragment() { ) binding.lifecycleOwner = viewLifecycleOwner - binding.login.setOnClickListener{ + binding.login.setOnClickListener { viewModel.onLogin( binding.email.text.toString(), binding.password.text.toString(), @@ -40,20 +40,20 @@ class AccountsFragment : Fragment() { ?.getString("fcmToken", null) ?: "" ) } - binding.signup.setOnClickListener{ + binding.signup.setOnClickListener { viewModel.onSignup( binding.email.text.toString(), binding.password.text.toString(), binding.name.text.toString() ) } - binding.getUser.setOnClickListener{ + binding.getUser.setOnClickListener { viewModel.getUser() } - binding.oAuth.setOnClickListener{ + binding.oAuth.setOnClickListener { viewModel.oAuthLogin(activity as ComponentActivity) } - binding.logout.setOnClickListener{ + binding.logout.setOnClickListener { viewModel.logout() } @@ -81,4 +81,4 @@ class AccountsFragment : Fragment() { return binding.root } -} \ No newline at end of file +} diff --git a/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt b/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt index 43023279..9ad9cbf9 100644 --- a/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt +++ b/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt @@ -113,4 +113,4 @@ class AccountsViewModel : ViewModel() { } } } -} \ No newline at end of file +} diff --git a/example/src/main/java/io/appwrite/android/utils/Client.kt b/example/src/main/java/io/appwrite/android/utils/Client.kt index f62869b0..2a0fce58 100644 --- a/example/src/main/java/io/appwrite/android/utils/Client.kt +++ b/example/src/main/java/io/appwrite/android/utils/Client.kt @@ -4,7 +4,7 @@ import android.content.Context import io.appwrite.Client object Client { - lateinit var client : Client + lateinit var client: Client fun create(context: Context) { client = Client(context) @@ -12,4 +12,4 @@ object Client { .setProject("65a8e2b4632c04b1f5da") .setSelfSigned(true) } -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 52ad5e71..c56b367b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 05f2bf0d..a526d1f2 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -80,13 +80,13 @@ dependencies { api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") api("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0") - api("com.squareup.okhttp3:okhttp:5.4.0") + api("com.squareup.okhttp3:okhttp:5.5.0") implementation("com.google.code.gson:gson:2.14.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0") implementation("androidx.lifecycle:lifecycle-common-java8:2.11.0") - implementation("androidx.appcompat:appcompat:1.7.1") - implementation("androidx.fragment:fragment-ktx:1.8.9") + implementation("androidx.appcompat:appcompat:1.8.0") + implementation("androidx.fragment:fragment-ktx:1.9.0") implementation("androidx.activity:activity-ktx:1.13.0") implementation("androidx.browser:browser:1.10.0") implementation("androidx.core:core-ktx:1.19.0") diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 2610f62d..55f4113a 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -7,7 +7,7 @@ import io.appwrite.cookies.ListenableCookieJar import io.appwrite.cookies.stores.SharedPreferencesCookieStore import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.fromJson -import io.appwrite.extensions.toJson +import io.appwrite.extensions.toJsonRequestBody import io.appwrite.models.InputFile import io.appwrite.models.UploadProgress import kotlinx.coroutines.CoroutineScope @@ -26,8 +26,8 @@ import okhttp3.RequestBody.Companion.toRequestBody import java.io.BufferedInputStream import java.io.BufferedReader import java.io.File -import java.io.RandomAccessFile import java.io.IOException +import java.io.RandomAccessFile import java.lang.IllegalArgumentException import java.net.CookieManager import java.net.CookiePolicy @@ -54,7 +54,7 @@ class Client @JvmOverloads constructor( /** * The size for chunked uploads in bytes. */ - internal const val CHUNK_SIZE = 5*1024*1024; // 5MB + internal const val CHUNK_SIZE = 5 * 1024 * 1024; // 5MB internal const val MAX_CONCURRENT_UPLOADS = 8 internal const val GLOBAL_PREFS = "io.appwrite" internal const val COOKIE_PREFS = "myCookie" @@ -94,8 +94,8 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "26.0.0", - "x-appwrite-response-format" to "1.9.5" + "x-sdk-version" to "27.0.0", + "x-appwrite-response-format" to "1.9.6" ) config = mutableMapOf() @@ -395,24 +395,22 @@ class Client @JvmOverloads constructor( } /** - * Send the HTTP request + * Prepare the HTTP request * * @param method * @param path * @param headers * @param params * - * @return [T] + * @return [Request] */ @Throws(AppwriteException::class) - suspend fun call( + suspend fun prepareRequest( method: String, path: String, - headers: Map = mapOf(), + headers: Map = mapOf(), params: Map = mapOf(), - responseType: Class, - converter: ((Any) -> T)? = null - ): T { + ): Request { val filteredParams = params.filterValues { it != null } val requestHeaders = this.headers.toHeaders().newBuilder() @@ -441,13 +439,12 @@ class Client @JvmOverloads constructor( } } } - val request = Request.Builder() + + return Request.Builder() .url(httpBuilder.build()) .headers(requestHeaders) .get() .build() - - return awaitResponse(request, responseType, converter) } val body = if (MultipartBody.FORM.toString() == headers["content-type"]) { @@ -474,17 +471,38 @@ class Client @JvmOverloads constructor( } builder.build() } else { - filteredParams - .toJson() + params + .toJsonRequestBody() .toRequestBody("application/json".toMediaType()) } - val request = Request.Builder() + return Request.Builder() .url(httpBuilder.build()) .headers(requestHeaders) .method(method, body) .build() + } + /** + * Send the HTTP request + * + * @param method + * @param path + * @param headers + * @param params + * + * @return [T] + */ + @Throws(AppwriteException::class) + suspend fun call( + method: String, + path: String, + headers: Map = mapOf(), + params: Map = mapOf(), + responseType: Class, + converter: ((Any) -> T)? = null + ): T { + val request = prepareRequest(method, path, headers, params) return awaitResponse(request, responseType, converter) } @@ -500,7 +518,7 @@ class Client @JvmOverloads constructor( @Throws(AppwriteException::class) suspend fun chunkedUpload( path: String, - headers: MutableMap, + headers: MutableMap, params: MutableMap, responseType: Class, converter: ((Any) -> T), @@ -509,7 +527,7 @@ class Client @JvmOverloads constructor( onProgress: ((UploadProgress) -> Unit)? = null, ): T { val input = params[paramName] as InputFile - val size: Long = when(input.sourceType) { + val size: Long = when (input.sourceType) { "path", "file" -> { File(input.path).length() } @@ -520,7 +538,7 @@ class Client @JvmOverloads constructor( } if (size < CHUNK_SIZE) { - val data = when(input.sourceType) { + val data = when (input.sourceType) { "file", "path" -> File(input.path).asRequestBody() "bytes" -> (input.data as ByteArray).toRequestBody(input.mimeType.toMediaType()) else -> throw UnsupportedOperationException() @@ -544,24 +562,25 @@ class Client @JvmOverloads constructor( var result: Map<*, *>? = null var uploadId: String? = null - if (idParamName?.isNotEmpty() == true) { + val providedUploadId = idParamName?.let { params[it]?.toString() } + if (!providedUploadId.isNullOrEmpty()) { // Make a request to check if a file already exists val current = call( method = "GET", - path = "$path/${params[idParamName]}", + path = "$path/$providedUploadId", headers = headers, params = emptyMap(), responseType = Map::class.java, ) val chunksUploaded = current["chunksUploaded"] as Long offset = chunksUploaded * CHUNK_SIZE - uploadId = params[idParamName]?.toString() + uploadId = providedUploadId result = current } fun readChunk(start: Long, end: Long): ByteArray { val length = (end - start).toInt() - return when(input.sourceType) { + return when (input.sourceType) { "file", "path" -> { RandomAccessFile(input.path, "r").use { chunkFile -> val chunk = ByteArray(length) diff --git a/library/src/main/java/io/appwrite/ID.kt b/library/src/main/java/io/appwrite/ID.kt index b89957b6..cf415258 100644 --- a/library/src/main/java/io/appwrite/ID.kt +++ b/library/src/main/java/io/appwrite/ID.kt @@ -24,7 +24,7 @@ class ID { fun custom(id: String): String = id - /** + /** * Generate a unique ID with padding to have a longer ID * * @param padding The number of characters to add to the ID diff --git a/library/src/main/java/io/appwrite/KeepAliveService.kt b/library/src/main/java/io/appwrite/KeepAliveService.kt index fb46f7a0..6faffe80 100644 --- a/library/src/main/java/io/appwrite/KeepAliveService.kt +++ b/library/src/main/java/io/appwrite/KeepAliveService.kt @@ -5,10 +5,10 @@ import android.content.Intent import android.os.Binder import android.os.IBinder -internal class KeepAliveService: Service() { +internal class KeepAliveService : Service() { companion object { - val binder = Binder() + val binder = Binder() } override fun onBind(intent: Intent) = binder -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/Permission.kt b/library/src/main/java/io/appwrite/Permission.kt index 462b8e4d..34d1a480 100644 --- a/library/src/main/java/io/appwrite/Permission.kt +++ b/library/src/main/java/io/appwrite/Permission.kt @@ -14,7 +14,7 @@ class Permission { */ fun read(role: String): String { return "read(\"${role}\")" - } + } /** * Generate write permission string for the provided role. @@ -27,8 +27,8 @@ class Permission { */ fun write(role: String): String { return "write(\"${role}\")" - } - + } + /** * Generate create permission string for the provided role. * @@ -37,8 +37,8 @@ class Permission { */ fun create(role: String): String { return "create(\"${role}\")" - } - + } + /** * Generate update permission string for the provided role. * @@ -47,8 +47,8 @@ class Permission { */ fun update(role: String): String { return "update(\"${role}\")" - } - + } + /** * Generate delete permission string for the provided role. * @@ -57,6 +57,6 @@ class Permission { */ fun delete(role: String): String { return "delete(\"${role}\")" - } + } } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/Query.kt b/library/src/main/java/io/appwrite/Query.kt index e6ba5b60..b0d4ab0f 100644 --- a/library/src/main/java/io/appwrite/Query.kt +++ b/library/src/main/java/io/appwrite/Query.kt @@ -1,7 +1,7 @@ package io.appwrite -import io.appwrite.extensions.toJson import io.appwrite.extensions.fromJson +import io.appwrite.extensions.toJson /** * Helper class to generate query strings. @@ -198,7 +198,7 @@ class Query( * @returns The query string. */ fun cursorAfter(documentId: String) = Query("cursorAfter", null, listOf(documentId)).toJson() - + /** * Return only limit results. * diff --git a/library/src/main/java/io/appwrite/Role.kt b/library/src/main/java/io/appwrite/Role.kt index da9ccb14..2348a024 100644 --- a/library/src/main/java/io/appwrite/Role.kt +++ b/library/src/main/java/io/appwrite/Role.kt @@ -19,7 +19,7 @@ class Role { * You can optionally pass verified or unverified for * [status] to target specific types of users. */ - fun user(id: String, status: String = ""): String = if(status.isEmpty()) { + fun user(id: String, status: String = ""): String = if (status.isEmpty()) { "user:$id" } else { "user:$id/$status" @@ -31,7 +31,7 @@ class Role { * You can optionally pass verified or unverified for * [status] to target specific types of users. */ - fun users(status: String = ""): String = if(status.isEmpty()) { + fun users(status: String = ""): String = if (status.isEmpty()) { "users" } else { "users/$status" @@ -50,7 +50,7 @@ class Role { * You can optionally pass a role for [role] to target * team members with the specified role. */ - fun team(id: String, role: String = ""): String = if(role.isEmpty()) { + fun team(id: String, role: String = ""): String = if (role.isEmpty()) { "team:$id" } else { "team:$id/$role" @@ -69,4 +69,4 @@ class Role { */ fun label(name: String): String = "label:$name" } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/WebAuthComponent.kt b/library/src/main/java/io/appwrite/WebAuthComponent.kt index 9db97f97..e49b7814 100644 --- a/library/src/main/java/io/appwrite/WebAuthComponent.kt +++ b/library/src/main/java/io/appwrite/WebAuthComponent.kt @@ -97,4 +97,4 @@ internal class WebAuthComponent { callbacks.clear() } } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/cookies/Extensions.kt b/library/src/main/java/io/appwrite/cookies/Extensions.kt index 257c904e..36c7dd13 100644 --- a/library/src/main/java/io/appwrite/cookies/Extensions.kt +++ b/library/src/main/java/io/appwrite/cookies/Extensions.kt @@ -50,4 +50,4 @@ fun CookieStore.syncToWebKitCookieManager() { fun android.webkit.CookieManager.removeAll() { removeAllCookies(null) flush() -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/cookies/InternalCookie.kt b/library/src/main/java/io/appwrite/cookies/InternalCookie.kt index c4ea22a9..dfb8948a 100644 --- a/library/src/main/java/io/appwrite/cookies/InternalCookie.kt +++ b/library/src/main/java/io/appwrite/cookies/InternalCookie.kt @@ -46,4 +46,4 @@ data class InternalCookie( isHttpOnly = (this@InternalCookie.httpOnly == true) } } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt b/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt index 1f2b536b..0c6f5bd9 100644 --- a/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt +++ b/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt @@ -189,7 +189,7 @@ open class InMemoryCookieStore : CookieStore { return host.equals(domain, ignoreCase = true) } else if (lengthDiff > 0) { // need to check H & D component - val D = host.substring(lengthDiff) + val hostDomain = host.substring(lengthDiff) // Android-changed: b/26456024 targetSdkVersion based compatibility for domain matching // Android M and earlier: Cookies with domain "foo.com" would not match "bar.foo.com". @@ -197,7 +197,7 @@ open class InMemoryCookieStore : CookieStore { // leading period and must therefore match "bar.foo.com". return if (Build.VERSION.SDK_INT <= 23 && !domain.startsWith(".")) { false - } else D.equals(domain, ignoreCase = true) + } else hostDomain.equals(domain, ignoreCase = true) } else if (lengthDiff == -1) { // if domain is actually .host return domain[0] == '.' && host.equals(domain.substring(1), ignoreCase = true) @@ -216,10 +216,9 @@ open class InMemoryCookieStore : CookieStore { for ((_, lst) in uriIndex) { for (c in lst) { val domain = c.domain - if (c.version == 0 && netscapeDomainMatches( - domain, - host - ) || c.version == 1 && HttpCookie.domainMatches(domain, host) + if ( + (c.version == 0 && netscapeDomainMatches(domain, host)) || + (c.version == 1 && HttpCookie.domainMatches(domain, host)) ) { if (!c.hasExpired()) { if (!cookies.contains(c)) { @@ -265,4 +264,4 @@ open class InMemoryCookieStore : CookieStore { return cookies } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt b/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt index 0c0b7601..61986f6c 100644 --- a/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt +++ b/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt @@ -1,11 +1,11 @@ package io.appwrite.cookies.stores -import io.appwrite.cookies.InternalCookie import android.content.SharedPreferences import android.os.Build import android.util.Log import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import io.appwrite.cookies.InternalCookie import java.net.HttpCookie import java.net.URI @@ -94,4 +94,4 @@ open class SharedPreferencesCookieStore( return result } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/coroutines/Callback.kt b/library/src/main/java/io/appwrite/coroutines/Callback.kt index baa30fe1..43fb98f5 100644 --- a/library/src/main/java/io/appwrite/coroutines/Callback.kt +++ b/library/src/main/java/io/appwrite/coroutines/Callback.kt @@ -15,4 +15,4 @@ class CoroutineCallback @JvmOverloads constructor( override fun resumeWith(result: Result) { callback.onComplete(result.getOrNull(), result.exceptionOrNull()) } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/AuthenticationFactor.kt b/library/src/main/java/io/appwrite/enums/AuthenticationFactor.kt index cf294cae..28aac01b 100644 --- a/library/src/main/java/io/appwrite/enums/AuthenticationFactor.kt +++ b/library/src/main/java/io/appwrite/enums/AuthenticationFactor.kt @@ -10,7 +10,9 @@ enum class AuthenticationFactor(val value: String) { @SerializedName("totp") TOTP("totp"), @SerializedName("recoverycode") - RECOVERYCODE("recoverycode"); + RECOVERYCODE("recoverycode"), + @SerializedName("custom") + CUSTOM("custom"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/AuthenticatorType.kt b/library/src/main/java/io/appwrite/enums/AuthenticatorType.kt index a7d37aec..11c2a9aa 100644 --- a/library/src/main/java/io/appwrite/enums/AuthenticatorType.kt +++ b/library/src/main/java/io/appwrite/enums/AuthenticatorType.kt @@ -7,4 +7,4 @@ enum class AuthenticatorType(val value: String) { TOTP("totp"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/Browser.kt b/library/src/main/java/io/appwrite/enums/Browser.kt index 9800111c..3ed75720 100644 --- a/library/src/main/java/io/appwrite/enums/Browser.kt +++ b/library/src/main/java/io/appwrite/enums/Browser.kt @@ -33,4 +33,4 @@ enum class Browser(val value: String) { OPERA_NEXT("on"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/BrowserPermission.kt b/library/src/main/java/io/appwrite/enums/BrowserPermission.kt index 339166a7..da572046 100644 --- a/library/src/main/java/io/appwrite/enums/BrowserPermission.kt +++ b/library/src/main/java/io/appwrite/enums/BrowserPermission.kt @@ -45,4 +45,4 @@ enum class BrowserPermission(val value: String) { XR_SPATIAL_TRACKING("xr-spatial-tracking"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/BrowserTheme.kt b/library/src/main/java/io/appwrite/enums/BrowserTheme.kt index 66584d24..3de304b5 100644 --- a/library/src/main/java/io/appwrite/enums/BrowserTheme.kt +++ b/library/src/main/java/io/appwrite/enums/BrowserTheme.kt @@ -9,4 +9,4 @@ enum class BrowserTheme(val value: String) { DARK("dark"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/CreditCard.kt b/library/src/main/java/io/appwrite/enums/CreditCard.kt index ab9b2151..b814a101 100644 --- a/library/src/main/java/io/appwrite/enums/CreditCard.kt +++ b/library/src/main/java/io/appwrite/enums/CreditCard.kt @@ -39,4 +39,4 @@ enum class CreditCard(val value: String) { RUPAY("rupay"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/ExecutionMethod.kt b/library/src/main/java/io/appwrite/enums/ExecutionMethod.kt index 1595981c..46629571 100644 --- a/library/src/main/java/io/appwrite/enums/ExecutionMethod.kt +++ b/library/src/main/java/io/appwrite/enums/ExecutionMethod.kt @@ -19,4 +19,4 @@ enum class ExecutionMethod(val value: String) { HEAD("HEAD"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/ExecutionResourceType.kt b/library/src/main/java/io/appwrite/enums/ExecutionResourceType.kt new file mode 100644 index 00000000..7a5d6419 --- /dev/null +++ b/library/src/main/java/io/appwrite/enums/ExecutionResourceType.kt @@ -0,0 +1,12 @@ +package io.appwrite.enums + +import com.google.gson.annotations.SerializedName + +enum class ExecutionResourceType(val value: String) { + @SerializedName("functions") + FUNCTIONS("functions"), + @SerializedName("sites") + SITES("sites"); + + override fun toString() = value +} diff --git a/library/src/main/java/io/appwrite/enums/ExecutionStatus.kt b/library/src/main/java/io/appwrite/enums/ExecutionStatus.kt index 25a1412d..f6d2406d 100644 --- a/library/src/main/java/io/appwrite/enums/ExecutionStatus.kt +++ b/library/src/main/java/io/appwrite/enums/ExecutionStatus.kt @@ -15,4 +15,4 @@ enum class ExecutionStatus(val value: String) { SCHEDULED("scheduled"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/ExecutionTrigger.kt b/library/src/main/java/io/appwrite/enums/ExecutionTrigger.kt index e8bb981f..3ba84bb5 100644 --- a/library/src/main/java/io/appwrite/enums/ExecutionTrigger.kt +++ b/library/src/main/java/io/appwrite/enums/ExecutionTrigger.kt @@ -11,4 +11,4 @@ enum class ExecutionTrigger(val value: String) { EVENT("event"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/Flag.kt b/library/src/main/java/io/appwrite/enums/Flag.kt index e4be0b8f..bd12ec7f 100644 --- a/library/src/main/java/io/appwrite/enums/Flag.kt +++ b/library/src/main/java/io/appwrite/enums/Flag.kt @@ -395,4 +395,4 @@ enum class Flag(val value: String) { ZIMBABWE("zw"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/ImageFormat.kt b/library/src/main/java/io/appwrite/enums/ImageFormat.kt index 8e74806f..77ea60d0 100644 --- a/library/src/main/java/io/appwrite/enums/ImageFormat.kt +++ b/library/src/main/java/io/appwrite/enums/ImageFormat.kt @@ -19,4 +19,4 @@ enum class ImageFormat(val value: String) { GIF("gif"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/ImageGravity.kt b/library/src/main/java/io/appwrite/enums/ImageGravity.kt index 45f8d882..58c51022 100644 --- a/library/src/main/java/io/appwrite/enums/ImageGravity.kt +++ b/library/src/main/java/io/appwrite/enums/ImageGravity.kt @@ -23,4 +23,4 @@ enum class ImageGravity(val value: String) { BOTTOM_RIGHT("bottom-right"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt index 7ef289d6..bc039175 100644 --- a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt +++ b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt @@ -43,6 +43,8 @@ enum class OAuthProvider(val value: String) { GITLAB("gitlab"), @SerializedName("google") GOOGLE("google"), + @SerializedName("huggingface") + HUGGINGFACE("huggingface"), @SerializedName("keycloak") KEYCLOAK("keycloak"), @SerializedName("kick") @@ -93,4 +95,4 @@ enum class OAuthProvider(val value: String) { ZOOM("zoom"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/enums/Timezone.kt b/library/src/main/java/io/appwrite/enums/Timezone.kt index 61d3920e..0f16bd4e 100644 --- a/library/src/main/java/io/appwrite/enums/Timezone.kt +++ b/library/src/main/java/io/appwrite/enums/Timezone.kt @@ -843,4 +843,4 @@ enum class Timezone(val value: String) { UTC("utc"); override fun toString() = value -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/exceptions/AppwriteException.kt b/library/src/main/java/io/appwrite/exceptions/AppwriteException.kt index 105f5ee3..85d39f50 100644 --- a/library/src/main/java/io/appwrite/exceptions/AppwriteException.kt +++ b/library/src/main/java/io/appwrite/exceptions/AppwriteException.kt @@ -7,4 +7,4 @@ class AppwriteException( val code: Int? = null, val type: String? = null, val response: String? = null -) : Exception(message) \ No newline at end of file +) : Exception(message) diff --git a/library/src/main/java/io/appwrite/extensions/CollectionExtensions.kt b/library/src/main/java/io/appwrite/extensions/CollectionExtensions.kt index a92704fc..727790b6 100644 --- a/library/src/main/java/io/appwrite/extensions/CollectionExtensions.kt +++ b/library/src/main/java/io/appwrite/extensions/CollectionExtensions.kt @@ -9,4 +9,4 @@ suspend fun Collection.forEachAsync( callback: suspend (T) -> Unit ) = withContext(IO) { map { async { callback.invoke(it) } }.awaitAll() -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt b/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt index 06d99404..17b5dc28 100644 --- a/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt +++ b/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt @@ -10,9 +10,30 @@ val gson: Gson = GsonBuilder() .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .create() +/** + * Request-body Gson. Default Gson omits nulls, which would drop keys the + * caller set to null inside nested maps such as `data`. Appwrite PATCH/upsert + * treats a missing key as "leave unchanged", so those nulls must be encoded. + */ +val requestGson: Gson = GsonBuilder() + .serializeNulls() + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create() + fun Any.toJson(): String = gson.toJson(this) +fun Any.toJsonWithNulls(): String = + requestGson.toJson(this) + +/** + * JSON request body: omit top-level keys the caller did not pass (optional + * method params default to null), but keep explicit nulls in nested maps. + */ +fun Map.toJsonRequestBody(): String = + filterValues { it != null }.toJsonWithNulls() + fun String.fromJson(clazz: Class): T = gson.fromJson(this, clazz) diff --git a/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt b/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt index e699a548..4f289cb1 100644 --- a/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt +++ b/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt @@ -6,4 +6,4 @@ import kotlin.reflect.typeOf inline fun classOf(): Class { @Suppress("UNCHECKED_CAST") return (typeOf().classifier!! as KClass).java -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/AlgoArgon2.kt b/library/src/main/java/io/appwrite/models/AlgoArgon2.kt index dee8c51e..39773328 100644 --- a/library/src/main/java/io/appwrite/models/AlgoArgon2.kt +++ b/library/src/main/java/io/appwrite/models/AlgoArgon2.kt @@ -40,7 +40,6 @@ data class AlgoArgon2( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt b/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt index a09e0fa8..38317807 100644 --- a/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt +++ b/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt @@ -19,7 +19,6 @@ data class AlgoBcrypt( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/AlgoMd5.kt b/library/src/main/java/io/appwrite/models/AlgoMd5.kt index 21b4d2c3..e46eb99b 100644 --- a/library/src/main/java/io/appwrite/models/AlgoMd5.kt +++ b/library/src/main/java/io/appwrite/models/AlgoMd5.kt @@ -19,7 +19,6 @@ data class AlgoMd5( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/AlgoPhpass.kt b/library/src/main/java/io/appwrite/models/AlgoPhpass.kt index 839b70b8..d457a81d 100644 --- a/library/src/main/java/io/appwrite/models/AlgoPhpass.kt +++ b/library/src/main/java/io/appwrite/models/AlgoPhpass.kt @@ -19,7 +19,6 @@ data class AlgoPhpass( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/AlgoScrypt.kt b/library/src/main/java/io/appwrite/models/AlgoScrypt.kt index 0d8af851..92147046 100644 --- a/library/src/main/java/io/appwrite/models/AlgoScrypt.kt +++ b/library/src/main/java/io/appwrite/models/AlgoScrypt.kt @@ -47,7 +47,6 @@ data class AlgoScrypt( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt b/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt index f0ac3517..539828ac 100644 --- a/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt +++ b/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt @@ -40,7 +40,6 @@ data class AlgoScryptModified( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/AlgoSha.kt b/library/src/main/java/io/appwrite/models/AlgoSha.kt index a366d584..45fb0104 100644 --- a/library/src/main/java/io/appwrite/models/AlgoSha.kt +++ b/library/src/main/java/io/appwrite/models/AlgoSha.kt @@ -19,7 +19,6 @@ data class AlgoSha( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/AppInstallation.kt b/library/src/main/java/io/appwrite/models/AppInstallation.kt new file mode 100644 index 00000000..46bf9075 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppInstallation.kt @@ -0,0 +1,101 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppInstallation + */ +data class AppInstallation( + /** + * Installation ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Installation creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Installation update time in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * ID of the installed application. + */ + @SerializedName("appId") + val appId: String, + + /** + * ID of the team the application is installed on. + */ + @SerializedName("teamId") + val teamId: String, + + /** + * Scopes granted to the application. Snapshot of the application's installation scopes taken when the installation was created or last updated. + */ + @SerializedName("scopes") + val scopes: List, + + /** + * Authorization details granted to the application. Rich authorization request (RFC 9396) style entries; the Appwrite Console stores authorized project IDs here. + */ + @SerializedName("authorizationDetails") + val authorizationDetails: List>, + + /** + * ID of the user who created the installation. + */ + @SerializedName("createdById") + val createdById: String, + + /** + * Name of the user who created the installation. + */ + @SerializedName("createdByName") + val createdByName: String, + + /** + * Time an access token was last issued for the installation in ISO 8601 format. Null if never used. + */ + @SerializedName("lastAccessedAt") + var lastAccessedAt: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "appId" to appId as Any, + "teamId" to teamId as Any, + "scopes" to scopes as Any, + "authorizationDetails" to authorizationDetails as Any, + "createdById" to createdById as Any, + "createdByName" to createdByName as Any, + "lastAccessedAt" to lastAccessedAt as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppInstallation( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + appId = map["appId"] as String, + teamId = map["teamId"] as String, + scopes = map["scopes"] as List, + authorizationDetails = map["authorizationDetails"] as List>, + createdById = map["createdById"] as String, + createdByName = map["createdByName"] as String, + lastAccessedAt = map["lastAccessedAt"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppInstallationList.kt b/library/src/main/java/io/appwrite/models/AppInstallationList.kt new file mode 100644 index 00000000..f7434cf2 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppInstallationList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App installations list + */ +data class AppInstallationList( + /** + * Total number of installations that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of installations. + */ + @SerializedName("installations") + val installations: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "installations" to installations.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppInstallationList( + total = (map["total"] as Number).toLong(), + installations = (map["installations"] as List>).map { AppInstallation.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Continent.kt b/library/src/main/java/io/appwrite/models/Continent.kt index f86090b0..0adbb5fa 100644 --- a/library/src/main/java/io/appwrite/models/Continent.kt +++ b/library/src/main/java/io/appwrite/models/Continent.kt @@ -26,7 +26,6 @@ data class Continent( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/ContinentList.kt b/library/src/main/java/io/appwrite/models/ContinentList.kt index 99476f26..53f95f2a 100644 --- a/library/src/main/java/io/appwrite/models/ContinentList.kt +++ b/library/src/main/java/io/appwrite/models/ContinentList.kt @@ -26,7 +26,6 @@ data class ContinentList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Country.kt b/library/src/main/java/io/appwrite/models/Country.kt index e1d818c4..7b104f0b 100644 --- a/library/src/main/java/io/appwrite/models/Country.kt +++ b/library/src/main/java/io/appwrite/models/Country.kt @@ -26,7 +26,6 @@ data class Country( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/CountryList.kt b/library/src/main/java/io/appwrite/models/CountryList.kt index 26e162ff..322cdaf8 100644 --- a/library/src/main/java/io/appwrite/models/CountryList.kt +++ b/library/src/main/java/io/appwrite/models/CountryList.kt @@ -26,7 +26,6 @@ data class CountryList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Currency.kt b/library/src/main/java/io/appwrite/models/Currency.kt index 421d1601..95ff13ac 100644 --- a/library/src/main/java/io/appwrite/models/Currency.kt +++ b/library/src/main/java/io/appwrite/models/Currency.kt @@ -61,7 +61,6 @@ data class Currency( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/CurrencyList.kt b/library/src/main/java/io/appwrite/models/CurrencyList.kt index fd391d36..8082a2f1 100644 --- a/library/src/main/java/io/appwrite/models/CurrencyList.kt +++ b/library/src/main/java/io/appwrite/models/CurrencyList.kt @@ -26,7 +26,6 @@ data class CurrencyList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Execution.kt b/library/src/main/java/io/appwrite/models/Execution.kt index f021fabe..662c48ea 100644 --- a/library/src/main/java/io/appwrite/models/Execution.kt +++ b/library/src/main/java/io/appwrite/models/Execution.kt @@ -1,9 +1,10 @@ package io.appwrite.models import com.google.gson.annotations.SerializedName -import io.appwrite.extensions.jsonCast +import io.appwrite.enums.ExecutionResourceType import io.appwrite.enums.ExecutionTrigger import io.appwrite.enums.ExecutionStatus +import io.appwrite.extensions.jsonCast /** * Execution @@ -34,25 +35,31 @@ data class Execution( val permissions: List, /** - * Function ID. + * Function or site ID. + */ + @SerializedName("resourceId") + val resourceId: String, + + /** + * Execution resource type. */ - @SerializedName("functionId") - val functionId: String, + @SerializedName("resourceType") + val resourceType: ExecutionResourceType, /** - * Function's deployment ID used to create the execution. + * Deployment ID used to create the execution. */ @SerializedName("deploymentId") val deploymentId: String, /** - * The trigger that caused the function to execute. Possible values can be: `http`, `schedule`, or `event`. + * The trigger that caused the resource to execute. Possible values can be: `http`, `schedule`, or `event`. */ @SerializedName("trigger") val trigger: ExecutionTrigger, /** - * The status of the function execution. Possible values can be: `waiting`, `processing`, `completed`, `failed`, or `scheduled`. + * The status of the resource execution. Possible values can be: `waiting`, `processing`, `completed`, `failed`, or `scheduled`. */ @SerializedName("status") val status: ExecutionStatus, @@ -94,13 +101,13 @@ data class Execution( val responseHeaders: List, /** - * Function logs. Includes the last 4,000 characters. This will return an empty string unless the response is returned using an API key or as part of a webhook payload. + * Resource logs. Includes the last 4,000 characters. This will return an empty string unless the response is returned using an API key or as part of a webhook payload. */ @SerializedName("logs") val logs: String, /** - * Function errors. Includes the last 4,000 characters. This will return an empty string unless the response is returned using an API key or as part of a webhook payload. + * Resource errors. Includes the last 4,000 characters. This will return an empty string unless the response is returned using an API key or as part of a webhook payload. */ @SerializedName("errors") val errors: String, @@ -123,7 +130,8 @@ data class Execution( "\$createdAt" to createdAt as Any, "\$updatedAt" to updatedAt as Any, "\$permissions" to permissions as Any, - "functionId" to functionId as Any, + "resourceId" to resourceId as Any, + "resourceType" to resourceType.value as Any, "deploymentId" to deploymentId as Any, "trigger" to trigger.value as Any, "status" to status.value as Any, @@ -140,7 +148,6 @@ data class Execution( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, @@ -149,7 +156,8 @@ data class Execution( createdAt = map["\$createdAt"] as String, updatedAt = map["\$updatedAt"] as String, permissions = map["\$permissions"] as List, - functionId = map["functionId"] as String, + resourceId = map["resourceId"] as String, + resourceType = ExecutionResourceType.values().find { it.value == map["resourceType"] as String }!!, deploymentId = map["deploymentId"] as String, trigger = ExecutionTrigger.values().find { it.value == map["trigger"] as String }!!, status = ExecutionStatus.values().find { it.value == map["status"] as String }!!, diff --git a/library/src/main/java/io/appwrite/models/ExecutionList.kt b/library/src/main/java/io/appwrite/models/ExecutionList.kt index 2b208bac..4eac1d89 100644 --- a/library/src/main/java/io/appwrite/models/ExecutionList.kt +++ b/library/src/main/java/io/appwrite/models/ExecutionList.kt @@ -26,7 +26,6 @@ data class ExecutionList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/File.kt b/library/src/main/java/io/appwrite/models/File.kt index 760c325f..d15a55b1 100644 --- a/library/src/main/java/io/appwrite/models/File.kt +++ b/library/src/main/java/io/appwrite/models/File.kt @@ -43,6 +43,18 @@ data class File( @SerializedName("name") val name: String, + /** + * Virtual folder containing the file, with a trailing slash. Empty for the bucket root. + */ + @SerializedName("folder") + val folder: String, + + /** + * Full virtual path of the file: the folder followed by the file name. + */ + @SerializedName("key") + val key: String, + /** * File MD5 signature. */ @@ -99,6 +111,8 @@ data class File( "\$updatedAt" to updatedAt as Any, "\$permissions" to permissions as Any, "name" to name as Any, + "folder" to folder as Any, + "key" to key as Any, "signature" to signature as Any, "mimeType" to mimeType as Any, "sizeOriginal" to sizeOriginal as Any, @@ -110,7 +124,6 @@ data class File( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, @@ -121,6 +134,8 @@ data class File( updatedAt = map["\$updatedAt"] as String, permissions = map["\$permissions"] as List, name = map["name"] as String, + folder = map["folder"] as String, + key = map["key"] as String, signature = map["signature"] as String, mimeType = map["mimeType"] as String, sizeOriginal = (map["sizeOriginal"] as Number).toLong(), diff --git a/library/src/main/java/io/appwrite/models/FileList.kt b/library/src/main/java/io/appwrite/models/FileList.kt index 60a1b676..7a462dc7 100644 --- a/library/src/main/java/io/appwrite/models/FileList.kt +++ b/library/src/main/java/io/appwrite/models/FileList.kt @@ -26,7 +26,6 @@ data class FileList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Headers.kt b/library/src/main/java/io/appwrite/models/Headers.kt index 1587bd17..4fece1e4 100644 --- a/library/src/main/java/io/appwrite/models/Headers.kt +++ b/library/src/main/java/io/appwrite/models/Headers.kt @@ -26,7 +26,6 @@ data class Headers( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Identity.kt b/library/src/main/java/io/appwrite/models/Identity.kt index 89e92b9d..ab70557a 100644 --- a/library/src/main/java/io/appwrite/models/Identity.kt +++ b/library/src/main/java/io/appwrite/models/Identity.kt @@ -82,7 +82,6 @@ data class Identity( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/IdentityList.kt b/library/src/main/java/io/appwrite/models/IdentityList.kt index 3dfe554d..b92f4e6a 100644 --- a/library/src/main/java/io/appwrite/models/IdentityList.kt +++ b/library/src/main/java/io/appwrite/models/IdentityList.kt @@ -26,7 +26,6 @@ data class IdentityList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/InputFile.kt b/library/src/main/java/io/appwrite/models/InputFile.kt index afbb2e99..a5b377aa 100644 --- a/library/src/main/java/io/appwrite/models/InputFile.kt +++ b/library/src/main/java/io/appwrite/models/InputFile.kt @@ -34,4 +34,4 @@ class InputFile private constructor() { sourceType = "bytes" } } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Jwt.kt b/library/src/main/java/io/appwrite/models/Jwt.kt index 0a62fda7..c188a853 100644 --- a/library/src/main/java/io/appwrite/models/Jwt.kt +++ b/library/src/main/java/io/appwrite/models/Jwt.kt @@ -19,7 +19,6 @@ data class Jwt( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Language.kt b/library/src/main/java/io/appwrite/models/Language.kt index 34ff473e..be1f30e8 100644 --- a/library/src/main/java/io/appwrite/models/Language.kt +++ b/library/src/main/java/io/appwrite/models/Language.kt @@ -33,7 +33,6 @@ data class Language( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/LanguageList.kt b/library/src/main/java/io/appwrite/models/LanguageList.kt index b68315fc..e1b0e6e7 100644 --- a/library/src/main/java/io/appwrite/models/LanguageList.kt +++ b/library/src/main/java/io/appwrite/models/LanguageList.kt @@ -26,7 +26,6 @@ data class LanguageList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Locale.kt b/library/src/main/java/io/appwrite/models/Locale.kt index 8fd26bc1..689de89c 100644 --- a/library/src/main/java/io/appwrite/models/Locale.kt +++ b/library/src/main/java/io/appwrite/models/Locale.kt @@ -138,7 +138,6 @@ data class Locale( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/LocaleCode.kt b/library/src/main/java/io/appwrite/models/LocaleCode.kt index b16bf4a4..602d9ed9 100644 --- a/library/src/main/java/io/appwrite/models/LocaleCode.kt +++ b/library/src/main/java/io/appwrite/models/LocaleCode.kt @@ -26,7 +26,6 @@ data class LocaleCode( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/LocaleCodeList.kt b/library/src/main/java/io/appwrite/models/LocaleCodeList.kt index 912cc0b8..e0c568a7 100644 --- a/library/src/main/java/io/appwrite/models/LocaleCodeList.kt +++ b/library/src/main/java/io/appwrite/models/LocaleCodeList.kt @@ -26,7 +26,6 @@ data class LocaleCodeList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Log.kt b/library/src/main/java/io/appwrite/models/Log.kt index fdde90d8..586841c0 100644 --- a/library/src/main/java/io/appwrite/models/Log.kt +++ b/library/src/main/java/io/appwrite/models/Log.kt @@ -166,7 +166,6 @@ data class Log( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/LogList.kt b/library/src/main/java/io/appwrite/models/LogList.kt index 22f1df59..ef51d7a7 100644 --- a/library/src/main/java/io/appwrite/models/LogList.kt +++ b/library/src/main/java/io/appwrite/models/LogList.kt @@ -26,7 +26,6 @@ data class LogList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Membership.kt b/library/src/main/java/io/appwrite/models/Membership.kt index ab0088b4..7d5b963b 100644 --- a/library/src/main/java/io/appwrite/models/Membership.kt +++ b/library/src/main/java/io/appwrite/models/Membership.kt @@ -117,7 +117,6 @@ data class Membership( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/MembershipList.kt b/library/src/main/java/io/appwrite/models/MembershipList.kt index 60d2677c..8376bc96 100644 --- a/library/src/main/java/io/appwrite/models/MembershipList.kt +++ b/library/src/main/java/io/appwrite/models/MembershipList.kt @@ -26,7 +26,6 @@ data class MembershipList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/MfaChallenge.kt b/library/src/main/java/io/appwrite/models/MfaChallenge.kt index 2eacbc92..b3e9f6bd 100644 --- a/library/src/main/java/io/appwrite/models/MfaChallenge.kt +++ b/library/src/main/java/io/appwrite/models/MfaChallenge.kt @@ -40,7 +40,6 @@ data class MfaChallenge( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/MfaFactors.kt b/library/src/main/java/io/appwrite/models/MfaFactors.kt index ac1a6a7a..20f7bb36 100644 --- a/library/src/main/java/io/appwrite/models/MfaFactors.kt +++ b/library/src/main/java/io/appwrite/models/MfaFactors.kt @@ -31,16 +31,22 @@ data class MfaFactors( @SerializedName("recoveryCode") val recoveryCode: Boolean, + /** + * Can custom factor be used for MFA challenge for this account. + */ + @SerializedName("custom") + val custom: Boolean, + ) { fun toMap(): Map = mapOf( "totp" to totp as Any, "phone" to phone as Any, "email" to email as Any, "recoveryCode" to recoveryCode as Any, + "custom" to custom as Any, ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, @@ -49,6 +55,7 @@ data class MfaFactors( phone = map["phone"] as Boolean, email = map["email"] as Boolean, recoveryCode = map["recoveryCode"] as Boolean, + custom = map["custom"] as Boolean, ) } } diff --git a/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt b/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt index 1bf2292f..a8967496 100644 --- a/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt +++ b/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt @@ -19,7 +19,6 @@ data class MfaRecoveryCodes( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/MfaType.kt b/library/src/main/java/io/appwrite/models/MfaType.kt index bb7f4667..e20b7f66 100644 --- a/library/src/main/java/io/appwrite/models/MfaType.kt +++ b/library/src/main/java/io/appwrite/models/MfaType.kt @@ -26,7 +26,6 @@ data class MfaType( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Oauth2Consent.kt b/library/src/main/java/io/appwrite/models/Oauth2Consent.kt new file mode 100644 index 00000000..7769e9b1 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Consent.kt @@ -0,0 +1,101 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Consent + */ +data class Oauth2Consent( + /** + * Consent ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Consent creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Consent update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * ID of the user the consent belongs to. + */ + @SerializedName("userId") + val userId: String, + + /** + * ID of the registered app the consent was given to. Empty for URL-form (CIMD) clients. + */ + @SerializedName("appId") + val appId: String, + + /** + * Client ID metadata document URL of the client the consent was given to. Empty for registered apps. + */ + @SerializedName("cimdUrl") + val cimdUrl: String, + + /** + * OAuth2 scopes the user consented to. + */ + @SerializedName("scopes") + val scopes: List, + + /** + * RFC 8707 resource indicators the user consented to. + */ + @SerializedName("resources") + val resources: List, + + /** + * Authorization details the user consented to, as a JSON string. Each entry has a `type` plus project-defined fields. + */ + @SerializedName("authorizationDetails") + val authorizationDetails: String, + + /** + * Consent expiration time in ISO 8601 format. Empty when the consent has no token-bound expiry yet. + */ + @SerializedName("expire") + val expire: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "userId" to userId as Any, + "appId" to appId as Any, + "cimdUrl" to cimdUrl as Any, + "scopes" to scopes as Any, + "resources" to resources as Any, + "authorizationDetails" to authorizationDetails as Any, + "expire" to expire as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Consent( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + userId = map["userId"] as String, + appId = map["appId"] as String, + cimdUrl = map["cimdUrl"] as String, + scopes = map["scopes"] as List, + resources = map["resources"] as List, + authorizationDetails = map["authorizationDetails"] as String, + expire = map["expire"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt b/library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt new file mode 100644 index 00000000..da6acd38 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 consents list + */ +data class Oauth2ConsentList( + /** + * Total number of consents that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of consents. + */ + @SerializedName("consents") + val consents: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "consents" to consents.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2ConsentList( + total = (map["total"] as Number).toLong(), + consents = (map["consents"] as List>).map { Oauth2Consent.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2ConsentToken.kt b/library/src/main/java/io/appwrite/models/Oauth2ConsentToken.kt new file mode 100644 index 00000000..31f6249b --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2ConsentToken.kt @@ -0,0 +1,109 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Consent Token + */ +data class Oauth2ConsentToken( + /** + * Token family ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Token creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Token update date in ISO 8601 format. Refreshing the token family updates this. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * ID of the consent the token family was issued under. + */ + @SerializedName("consentId") + val consentId: String, + + /** + * ID of the user the token family belongs to. + */ + @SerializedName("userId") + val userId: String, + + /** + * ID of the registered app the token family was issued to. Empty for URL-form (CIMD) clients. + */ + @SerializedName("appId") + val appId: String, + + /** + * Client ID metadata document URL of the client the token family was issued to. Empty for registered apps. + */ + @SerializedName("cimdUrl") + val cimdUrl: String, + + /** + * OAuth2 scopes granted on the token family. + */ + @SerializedName("scopes") + val scopes: List, + + /** + * RFC 8707 resource indicators granted on the token family. + */ + @SerializedName("resources") + val resources: List, + + /** + * Authorization details granted on the token family, as a JSON string. Each entry has a `type` plus project-defined fields. + */ + @SerializedName("authorizationDetails") + val authorizationDetails: String, + + /** + * Expiration time of the current access token of this family in ISO 8601 format. + */ + @SerializedName("expire") + val expire: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "consentId" to consentId as Any, + "userId" to userId as Any, + "appId" to appId as Any, + "cimdUrl" to cimdUrl as Any, + "scopes" to scopes as Any, + "resources" to resources as Any, + "authorizationDetails" to authorizationDetails as Any, + "expire" to expire as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2ConsentToken( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + consentId = map["consentId"] as String, + userId = map["userId"] as String, + appId = map["appId"] as String, + cimdUrl = map["cimdUrl"] as String, + scopes = map["scopes"] as List, + resources = map["resources"] as List, + authorizationDetails = map["authorizationDetails"] as String, + expire = map["expire"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt b/library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt new file mode 100644 index 00000000..09278f45 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 consent tokens list + */ +data class Oauth2ConsentTokenList( + /** + * Total number of tokens that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of tokens. + */ + @SerializedName("tokens") + val tokens: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "tokens" to tokens.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2ConsentTokenList( + total = (map["total"] as Number).toLong(), + tokens = (map["tokens"] as List>).map { Oauth2ConsentToken.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Phone.kt b/library/src/main/java/io/appwrite/models/Phone.kt index 88d19af3..74bfa518 100644 --- a/library/src/main/java/io/appwrite/models/Phone.kt +++ b/library/src/main/java/io/appwrite/models/Phone.kt @@ -33,7 +33,6 @@ data class Phone( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/PhoneList.kt b/library/src/main/java/io/appwrite/models/PhoneList.kt index 7b7507fd..fc0ffa3c 100644 --- a/library/src/main/java/io/appwrite/models/PhoneList.kt +++ b/library/src/main/java/io/appwrite/models/PhoneList.kt @@ -26,7 +26,6 @@ data class PhoneList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Presence.kt b/library/src/main/java/io/appwrite/models/Presence.kt index 0b25aec3..c7f60731 100644 --- a/library/src/main/java/io/appwrite/models/Presence.kt +++ b/library/src/main/java/io/appwrite/models/Presence.kt @@ -59,7 +59,7 @@ data class Presence( * Presence metadata. */ @SerializedName("metadata") - var metadata: Any?, + var metadata: Map?, ) { fun toMap(): Map = mapOf( @@ -75,7 +75,6 @@ data class Presence( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, @@ -88,7 +87,7 @@ data class Presence( status = map["status"] as? String, source = map["source"] as String, expiresAt = map["expiresAt"] as? String, - metadata = map["metadata"] as? Any, + metadata = map["metadata"] as? Map, ) } } diff --git a/library/src/main/java/io/appwrite/models/PresenceList.kt b/library/src/main/java/io/appwrite/models/PresenceList.kt index 58609304..b69d3e13 100644 --- a/library/src/main/java/io/appwrite/models/PresenceList.kt +++ b/library/src/main/java/io/appwrite/models/PresenceList.kt @@ -26,7 +26,6 @@ data class PresenceList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/RealtimeModels.kt b/library/src/main/java/io/appwrite/models/RealtimeModels.kt index 23caa5ae..ed55bbb1 100644 --- a/library/src/main/java/io/appwrite/models/RealtimeModels.kt +++ b/library/src/main/java/io/appwrite/models/RealtimeModels.kt @@ -51,4 +51,4 @@ data class RealtimeResponseEvent( enum class RealtimeCode(val value: Int) { POLICY_VIOLATION(1008), UNKNOWN_ERROR(-1) -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/models/Session.kt b/library/src/main/java/io/appwrite/models/Session.kt index 7bda2aeb..c9c39509 100644 --- a/library/src/main/java/io/appwrite/models/Session.kt +++ b/library/src/main/java/io/appwrite/models/Session.kt @@ -215,7 +215,6 @@ data class Session( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/SessionList.kt b/library/src/main/java/io/appwrite/models/SessionList.kt index e7368cb8..bda39fca 100644 --- a/library/src/main/java/io/appwrite/models/SessionList.kt +++ b/library/src/main/java/io/appwrite/models/SessionList.kt @@ -26,7 +26,6 @@ data class SessionList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Subscriber.kt b/library/src/main/java/io/appwrite/models/Subscriber.kt index 467920fe..931b65db 100644 --- a/library/src/main/java/io/appwrite/models/Subscriber.kt +++ b/library/src/main/java/io/appwrite/models/Subscriber.kt @@ -75,7 +75,6 @@ data class Subscriber( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Target.kt b/library/src/main/java/io/appwrite/models/Target.kt index caa87117..3e89e08d 100644 --- a/library/src/main/java/io/appwrite/models/Target.kt +++ b/library/src/main/java/io/appwrite/models/Target.kt @@ -75,7 +75,6 @@ data class Target( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Token.kt b/library/src/main/java/io/appwrite/models/Token.kt index b4e35161..625e3085 100644 --- a/library/src/main/java/io/appwrite/models/Token.kt +++ b/library/src/main/java/io/appwrite/models/Token.kt @@ -54,7 +54,6 @@ data class Token( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/Transaction.kt b/library/src/main/java/io/appwrite/models/Transaction.kt index f9e86be0..8f09860e 100644 --- a/library/src/main/java/io/appwrite/models/Transaction.kt +++ b/library/src/main/java/io/appwrite/models/Transaction.kt @@ -54,7 +54,6 @@ data class Transaction( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/TransactionList.kt b/library/src/main/java/io/appwrite/models/TransactionList.kt index c64ca151..c55a7317 100644 --- a/library/src/main/java/io/appwrite/models/TransactionList.kt +++ b/library/src/main/java/io/appwrite/models/TransactionList.kt @@ -26,7 +26,6 @@ data class TransactionList( ) companion object { - @Suppress("UNCHECKED_CAST") fun from( map: Map, diff --git a/library/src/main/java/io/appwrite/models/UploadProgress.kt b/library/src/main/java/io/appwrite/models/UploadProgress.kt index 4199694e..7a1663cf 100644 --- a/library/src/main/java/io/appwrite/models/UploadProgress.kt +++ b/library/src/main/java/io/appwrite/models/UploadProgress.kt @@ -6,4 +6,4 @@ data class UploadProgress( val sizeUploaded: Long, val chunksTotal: Int, val chunksUploaded: Int -) \ No newline at end of file +) diff --git a/library/src/main/java/io/appwrite/models/User.kt b/library/src/main/java/io/appwrite/models/User.kt index fd3d3760..473cb7fd 100644 --- a/library/src/main/java/io/appwrite/models/User.kt +++ b/library/src/main/java/io/appwrite/models/User.kt @@ -47,7 +47,7 @@ data class User( * Password hashing algorithm configuration. */ @SerializedName("hashOptions") - var hashOptions: Any?, + var hashOptions: Map?, /** * User registration date in ISO 8601 format. @@ -201,7 +201,7 @@ data class User( name: String, password: String?, hash: String?, - hashOptions: Any?, + hashOptions: Map?, registration: String, status: Boolean, labels: List, @@ -261,7 +261,7 @@ data class User( name = map["name"] as String, password = map["password"] as? String, hash = map["hash"] as? String, - hashOptions = map["hashOptions"] as? Any, + hashOptions = map["hashOptions"] as? Map, registration = map["registration"] as String, status = map["status"] as Boolean, labels = map["labels"] as List, diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index 633572de..495a4e3a 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -1,23 +1,21 @@ package io.appwrite.services import android.net.Uri +import androidx.activity.ComponentActivity import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* +import io.appwrite.WebAuthComponent import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf -import io.appwrite.WebAuthComponent -import androidx.activity.ComponentActivity +import io.appwrite.models.* import okhttp3.Cookie import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Account service allows you to authenticate and manage a user account. - */ + * The Account service allows you to authenticate and manage a user account. */ class Account(client: Client) : Service(client) { - /** * Get the currently logged in user. * @@ -26,11 +24,8 @@ class Account(client: Client) : Service(client) { suspend fun get( nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account" - ) - - val apiParams = mutableMapOf( - ) + val apiPath = "/account" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -55,8 +50,7 @@ class Account(client: Client) : Service(client) { * @return [io.appwrite.models.User] */ @Throws(AppwriteException::class) - suspend fun get( - ): io.appwrite.models.User> = get( + suspend fun get(): io.appwrite.models.User> = get( nestedType = classOf(), ) @@ -77,9 +71,7 @@ class Account(client: Client) : Service(client) { name: String? = null, nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account" - ) - + val apiPath = "/account" val apiParams = mutableMapOf( "userId" to userId, "email" to email, @@ -129,10 +121,206 @@ class Account(client: Client) : Service(client) { nestedType = classOf(), ) + /** + * Get a list of the OAuth2 consents the current user has given to third-party apps. + * + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.Oauth2ConsentList] + */ + @JvmOverloads + suspend fun listConsents( + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.Oauth2ConsentList { + val apiPath = "/account/consents" + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2ConsentList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2ConsentList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2ConsentList::class.java, + converter, + ) + } + + /** + * Get an OAuth2 consent the current user has given to a third-party app by its unique ID. + * + * @param consentId Consent unique ID. + * @return [io.appwrite.models.Oauth2Consent] + */ + suspend fun getConsent( + consentId: String, + ): io.appwrite.models.Oauth2Consent { + val apiPath = ("/account/consents/{consentId}" + .replace("{consentId}", consentId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Consent = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Consent.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Consent::class.java, + converter, + ) + } + + /** + * Delete an OAuth2 consent by its unique ID. All token families issued under the consent are revoked, and the app must ask for consent again to regain access. + * + * @param consentId Consent unique ID. + * @return [Any] + */ + suspend fun deleteConsent( + consentId: String, + ): Any { + val apiPath = ("/account/consents/{consentId}" + .replace("{consentId}", consentId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Get a list of the token families issued under an OAuth2 consent. Each entry represents one authorized device or session; the token secrets themselves are never returned. + * + * @param consentId Consent unique ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.Oauth2ConsentTokenList] + */ + @JvmOverloads + suspend fun listConsentTokens( + consentId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.Oauth2ConsentTokenList { + val apiPath = ("/account/consents/{consentId}/tokens" + .replace("{consentId}", consentId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2ConsentTokenList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2ConsentTokenList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2ConsentTokenList::class.java, + converter, + ) + } + + /** + * Get a token family issued under an OAuth2 consent by its unique ID. The token secrets themselves are never returned. + * + * @param consentId Consent unique ID. + * @param tokenId Token unique ID. + * @return [io.appwrite.models.Oauth2ConsentToken] + */ + suspend fun getConsentToken( + consentId: String, + tokenId: String, + ): io.appwrite.models.Oauth2ConsentToken { + val apiPath = ("/account/consents/{consentId}/tokens/{tokenId}" + .replace("{consentId}", consentId) + .replace("{tokenId}", tokenId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2ConsentToken = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2ConsentToken.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2ConsentToken::class.java, + converter, + ) + } + + /** + * Delete a token family issued under an OAuth2 consent by its unique ID. The access and refresh tokens of the family stop working immediately; other token families and the consent itself are unaffected. + * + * @param consentId Consent unique ID. + * @param tokenId Token unique ID. + * @return [Any] + */ + suspend fun deleteConsentToken( + consentId: String, + tokenId: String, + ): Any { + val apiPath = ("/account/consents/{consentId}/tokens/{tokenId}" + .replace("{consentId}", consentId) + .replace("{tokenId}", tokenId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + /** * Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request. * This endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password. - * + * * * @param email User email. * @param password User password. Must be at least 8 chars. @@ -143,9 +331,7 @@ class Account(client: Client) : Service(client) { password: String, nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account/email" - ) - + val apiPath = "/account/email" val apiParams = mutableMapOf( "email" to email, "password" to password, @@ -172,7 +358,7 @@ class Account(client: Client) : Service(client) { /** * Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request. * This endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password. - * + * * * @param email User email. * @param password User password. Must be at least 8 chars. @@ -200,9 +386,7 @@ class Account(client: Client) : Service(client) { queries: List? = null, total: Boolean? = null, ): io.appwrite.models.IdentityList { - val apiPath = ("/account/identities" - ) - + val apiPath = "/account/identities" val apiParams = mutableMapOf( "queries" to queries, "total" to total, @@ -225,7 +409,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Delete an identity by its unique ID. * @@ -238,9 +421,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/identities/{identityId}" .replace("{identityId}", identityId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -254,7 +435,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to create a JSON Web Token. You can use the resulting JWT to authenticate on behalf of the current user when working with the Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes from its creation and will be invalid if the user will logout in that time frame. * @@ -265,9 +445,7 @@ class Account(client: Client) : Service(client) { suspend fun createJWT( duration: Long? = null, ): io.appwrite.models.Jwt { - val apiPath = ("/account/jwts" - ) - + val apiPath = "/account/jwts" val apiParams = mutableMapOf( "duration" to duration, ) @@ -290,7 +468,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Get the list of latest security activity logs for the currently logged in user. Each log returns user IP address, location and date and time of log. * @@ -303,9 +480,7 @@ class Account(client: Client) : Service(client) { queries: List? = null, total: Boolean? = null, ): io.appwrite.models.LogList { - val apiPath = ("/account/logs" - ) - + val apiPath = "/account/logs" val apiParams = mutableMapOf( "queries" to queries, "total" to total, @@ -328,7 +503,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Enable or disable MFA on an account. * @@ -339,9 +513,7 @@ class Account(client: Client) : Service(client) { mfa: Boolean, nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account/mfa" - ) - + val apiPath = "/account/mfa" val apiParams = mutableMapOf( "mfa" to mfa, ) @@ -394,9 +566,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/mfa/authenticators/{type}" .replace("{type}", type.value) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -416,7 +586,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Add an authenticator app to be used as an MFA factor. Verify the authenticator using the [verify authenticator](/docs/references/cloud/client-web/account#updateMfaAuthenticator) method. * @@ -429,9 +598,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/mfa/authenticators/{type}" .replace("{type}", type.value) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -451,7 +618,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. * @@ -471,7 +637,6 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/mfa/authenticators/{type}" .replace("{type}", type.value) ) - val apiParams = mutableMapOf( "otp" to otp, ) @@ -530,7 +695,6 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/mfa/authenticators/{type}" .replace("{type}", type.value) ) - val apiParams = mutableMapOf( "otp" to otp, ) @@ -586,9 +750,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/mfa/authenticators/{type}" .replace("{type}", type.value) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -602,7 +764,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Delete an authenticator for a user by ID. * @@ -615,9 +776,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/mfa/authenticators/{type}" .replace("{type}", type.value) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -631,11 +790,10 @@ class Account(client: Client) : Service(client) { ) } - /** * Begin the process of MFA verification after sign-in. Finish the flow with [updateMfaChallenge](/docs/references/cloud/client-web/account#updateMfaChallenge) method. * - * @param factor Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`. + * @param factor Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`, `custom`. * @return [io.appwrite.models.MfaChallenge] */ @Deprecated( @@ -645,9 +803,7 @@ class Account(client: Client) : Service(client) { suspend fun createMfaChallenge( factor: io.appwrite.enums.AuthenticationFactor, ): io.appwrite.models.MfaChallenge { - val apiPath = ("/account/mfa/challenges" - ) - + val apiPath = "/account/mfa/challenges" val apiParams = mutableMapOf( "factor" to factor, ) @@ -670,19 +826,16 @@ class Account(client: Client) : Service(client) { ) } - /** * Begin the process of MFA verification after sign-in. Finish the flow with [updateMfaChallenge](/docs/references/cloud/client-web/account#updateMfaChallenge) method. * - * @param factor Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`. + * @param factor Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`, `custom`. * @return [io.appwrite.models.MfaChallenge] */ suspend fun createMFAChallenge( factor: io.appwrite.enums.AuthenticationFactor, ): io.appwrite.models.MfaChallenge { - val apiPath = ("/account/mfa/challenges" - ) - + val apiPath = "/account/mfa/challenges" val apiParams = mutableMapOf( "factor" to factor, ) @@ -705,7 +858,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Complete the MFA challenge by providing the one-time password. Finish the process of MFA verification by providing the one-time password. To begin the flow, use [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) method. * @@ -721,9 +873,7 @@ class Account(client: Client) : Service(client) { challengeId: String, otp: String, ): io.appwrite.models.Session { - val apiPath = ("/account/mfa/challenges" - ) - + val apiPath = "/account/mfa/challenges" val apiParams = mutableMapOf( "challengeId" to challengeId, "otp" to otp, @@ -747,7 +897,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Complete the MFA challenge by providing the one-time password. Finish the process of MFA verification by providing the one-time password. To begin the flow, use [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) method. * @@ -759,9 +908,7 @@ class Account(client: Client) : Service(client) { challengeId: String, otp: String, ): io.appwrite.models.Session { - val apiPath = ("/account/mfa/challenges" - ) - + val apiPath = "/account/mfa/challenges" val apiParams = mutableMapOf( "challengeId" to challengeId, "otp" to otp, @@ -785,7 +932,6 @@ class Account(client: Client) : Service(client) { ) } - /** * List the factors available on the account to be used as a MFA challange. * @@ -795,13 +941,9 @@ class Account(client: Client) : Service(client) { message = "This API has been deprecated since 1.8.0. Please use `Account.listMFAFactors` instead.", replaceWith = ReplaceWith("io.appwrite.services.Account.listMFAFactors") ) - suspend fun listMfaFactors( - ): io.appwrite.models.MfaFactors { - val apiPath = ("/account/mfa/factors" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listMfaFactors(): io.appwrite.models.MfaFactors { + val apiPath = "/account/mfa/factors" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -820,19 +962,14 @@ class Account(client: Client) : Service(client) { ) } - /** * List the factors available on the account to be used as a MFA challange. * * @return [io.appwrite.models.MfaFactors] */ - suspend fun listMFAFactors( - ): io.appwrite.models.MfaFactors { - val apiPath = ("/account/mfa/factors" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listMFAFactors(): io.appwrite.models.MfaFactors { + val apiPath = "/account/mfa/factors" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -851,7 +988,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Get recovery codes that can be used as backup for MFA flow. Before getting codes, they must be generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to read recovery codes. * @@ -861,13 +997,9 @@ class Account(client: Client) : Service(client) { message = "This API has been deprecated since 1.8.0. Please use `Account.getMFARecoveryCodes` instead.", replaceWith = ReplaceWith("io.appwrite.services.Account.getMFARecoveryCodes") ) - suspend fun getMfaRecoveryCodes( - ): io.appwrite.models.MfaRecoveryCodes { - val apiPath = ("/account/mfa/recovery-codes" - ) - - val apiParams = mutableMapOf( - ) + suspend fun getMfaRecoveryCodes(): io.appwrite.models.MfaRecoveryCodes { + val apiPath = "/account/mfa/recovery-codes" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -886,19 +1018,14 @@ class Account(client: Client) : Service(client) { ) } - /** * Get recovery codes that can be used as backup for MFA flow. Before getting codes, they must be generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to read recovery codes. * * @return [io.appwrite.models.MfaRecoveryCodes] */ - suspend fun getMFARecoveryCodes( - ): io.appwrite.models.MfaRecoveryCodes { - val apiPath = ("/account/mfa/recovery-codes" - ) - - val apiParams = mutableMapOf( - ) + suspend fun getMFARecoveryCodes(): io.appwrite.models.MfaRecoveryCodes { + val apiPath = "/account/mfa/recovery-codes" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -917,7 +1044,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Generate recovery codes as backup for MFA flow. It's recommended to generate and show then immediately after user successfully adds their authehticator. Recovery codes can be used as a MFA verification type in [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) method. * @@ -927,13 +1053,9 @@ class Account(client: Client) : Service(client) { message = "This API has been deprecated since 1.8.0. Please use `Account.createMFARecoveryCodes` instead.", replaceWith = ReplaceWith("io.appwrite.services.Account.createMFARecoveryCodes") ) - suspend fun createMfaRecoveryCodes( - ): io.appwrite.models.MfaRecoveryCodes { - val apiPath = ("/account/mfa/recovery-codes" - ) - - val apiParams = mutableMapOf( - ) + suspend fun createMfaRecoveryCodes(): io.appwrite.models.MfaRecoveryCodes { + val apiPath = "/account/mfa/recovery-codes" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -953,19 +1075,14 @@ class Account(client: Client) : Service(client) { ) } - /** * Generate recovery codes as backup for MFA flow. It's recommended to generate and show then immediately after user successfully adds their authehticator. Recovery codes can be used as a MFA verification type in [createMfaChallenge](/docs/references/cloud/client-web/account#createMfaChallenge) method. * * @return [io.appwrite.models.MfaRecoveryCodes] */ - suspend fun createMFARecoveryCodes( - ): io.appwrite.models.MfaRecoveryCodes { - val apiPath = ("/account/mfa/recovery-codes" - ) - - val apiParams = mutableMapOf( - ) + suspend fun createMFARecoveryCodes(): io.appwrite.models.MfaRecoveryCodes { + val apiPath = "/account/mfa/recovery-codes" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -985,7 +1102,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Regenerate recovery codes that can be used as backup for MFA flow. Before regenerating codes, they must be first generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to regenreate recovery codes. * @@ -995,13 +1111,9 @@ class Account(client: Client) : Service(client) { message = "This API has been deprecated since 1.8.0. Please use `Account.updateMFARecoveryCodes` instead.", replaceWith = ReplaceWith("io.appwrite.services.Account.updateMFARecoveryCodes") ) - suspend fun updateMfaRecoveryCodes( - ): io.appwrite.models.MfaRecoveryCodes { - val apiPath = ("/account/mfa/recovery-codes" - ) - - val apiParams = mutableMapOf( - ) + suspend fun updateMfaRecoveryCodes(): io.appwrite.models.MfaRecoveryCodes { + val apiPath = "/account/mfa/recovery-codes" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1021,19 +1133,14 @@ class Account(client: Client) : Service(client) { ) } - /** * Regenerate recovery codes that can be used as backup for MFA flow. Before regenerating codes, they must be first generated using [createMfaRecoveryCodes](/docs/references/cloud/client-web/account#createMfaRecoveryCodes) method. An OTP challenge is required to regenreate recovery codes. * * @return [io.appwrite.models.MfaRecoveryCodes] */ - suspend fun updateMFARecoveryCodes( - ): io.appwrite.models.MfaRecoveryCodes { - val apiPath = ("/account/mfa/recovery-codes" - ) - - val apiParams = mutableMapOf( - ) + suspend fun updateMFARecoveryCodes(): io.appwrite.models.MfaRecoveryCodes { + val apiPath = "/account/mfa/recovery-codes" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1053,7 +1160,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Update currently logged in user account name. * @@ -1064,9 +1170,7 @@ class Account(client: Client) : Service(client) { name: String, nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account/name" - ) - + val apiPath = "/account/name" val apiParams = mutableMapOf( "name" to name, ) @@ -1116,9 +1220,7 @@ class Account(client: Client) : Service(client) { oldPassword: String? = null, nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account/password" - ) - + val apiPath = "/account/password" val apiParams = mutableMapOf( "password" to password, "oldPassword" to oldPassword, @@ -1172,9 +1274,7 @@ class Account(client: Client) : Service(client) { password: String, nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account/phone" - ) - + val apiPath = "/account/phone" val apiParams = mutableMapOf( "phone" to phone, "password" to password, @@ -1223,11 +1323,8 @@ class Account(client: Client) : Service(client) { suspend fun getPrefs( nestedType: Class, ): io.appwrite.models.Preferences { - val apiPath = ("/account/prefs" - ) - - val apiParams = mutableMapOf( - ) + val apiPath = "/account/prefs" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -1252,8 +1349,7 @@ class Account(client: Client) : Service(client) { * @return [io.appwrite.models.Preferences] */ @Throws(AppwriteException::class) - suspend fun getPrefs( - ): io.appwrite.models.Preferences> = getPrefs( + suspend fun getPrefs(): io.appwrite.models.Preferences> = getPrefs( nestedType = classOf(), ) @@ -1264,12 +1360,10 @@ class Account(client: Client) : Service(client) { * @return [io.appwrite.models.User] */ suspend fun updatePrefs( - prefs: Any, + prefs: Map, nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account/prefs" - ) - + val apiPath = "/account/prefs" val apiParams = mutableMapOf( "prefs" to prefs, ) @@ -1300,7 +1394,7 @@ class Account(client: Client) : Service(client) { */ @Throws(AppwriteException::class) suspend fun updatePrefs( - prefs: Any, + prefs: Map, ): io.appwrite.models.User> = updatePrefs( prefs, nestedType = classOf(), @@ -1317,9 +1411,7 @@ class Account(client: Client) : Service(client) { email: String, url: String, ): io.appwrite.models.Token { - val apiPath = ("/account/recovery" - ) - + val apiPath = "/account/recovery" val apiParams = mutableMapOf( "email" to email, "url" to url, @@ -1343,10 +1435,9 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/recovery](https://appwrite.io/docs/references/cloud/client-web/account#createRecovery) endpoint. - * + * * Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. * * @param userId User ID. @@ -1359,9 +1450,7 @@ class Account(client: Client) : Service(client) { secret: String, password: String, ): io.appwrite.models.Token { - val apiPath = ("/account/recovery" - ) - + val apiPath = "/account/recovery" val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -1386,19 +1475,14 @@ class Account(client: Client) : Service(client) { ) } - /** * Get the list of active sessions across different devices for the currently logged in user. * * @return [io.appwrite.models.SessionList] */ - suspend fun listSessions( - ): io.appwrite.models.SessionList { - val apiPath = ("/account/sessions" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listSessions(): io.appwrite.models.SessionList { + val apiPath = "/account/sessions" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -1417,19 +1501,14 @@ class Account(client: Client) : Service(client) { ) } - /** * Delete all sessions from the user account and remove any sessions cookies from the end client. * * @return [Any] */ - suspend fun deleteSessions( - ): Any { - val apiPath = ("/account/sessions" - ) - - val apiParams = mutableMapOf( - ) + suspend fun deleteSessions(): Any { + val apiPath = "/account/sessions" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1443,19 +1522,14 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to allow a new user to register an anonymous account in your project. This route will also create a new session for the user. To allow the new user to convert an anonymous account to a normal account, you need to update its [email and password](https://appwrite.io/docs/references/cloud/client-web/account#updateEmail) or create an [OAuth2 session](https://appwrite.io/docs/references/cloud/client-web/account#CreateOAuth2Session). * * @return [io.appwrite.models.Session] */ - suspend fun createAnonymousSession( - ): io.appwrite.models.Session { - val apiPath = ("/account/sessions/anonymous" - ) - - val apiParams = mutableMapOf( - ) + suspend fun createAnonymousSession(): io.appwrite.models.Session { + val apiPath = "/account/sessions/anonymous" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1475,10 +1549,9 @@ class Account(client: Client) : Service(client) { ) } - /** * Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. - * + * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * @param email User email. @@ -1489,9 +1562,7 @@ class Account(client: Client) : Service(client) { email: String, password: String, ): io.appwrite.models.Session { - val apiPath = ("/account/sessions/email" - ) - + val apiPath = "/account/sessions/email" val apiParams = mutableMapOf( "email" to email, "password" to password, @@ -1515,7 +1586,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to create a session from token. Provide the **userId** and **secret** parameters from the successful response of authentication flows initiated by token creation. For example, magic URL and phone login. * @@ -1531,9 +1601,7 @@ class Account(client: Client) : Service(client) { userId: String, secret: String, ): io.appwrite.models.Session { - val apiPath = ("/account/sessions/magic-url" - ) - + val apiPath = "/account/sessions/magic-url" val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -1557,16 +1625,15 @@ class Account(client: Client) : Service(client) { ) } - /** * Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. - * + * * If there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user. - * + * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). - * * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -1582,7 +1649,6 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/sessions/oauth2/{provider}" .replace("{provider}", provider.value) ) - val apiParams = mutableMapOf( "success" to success, "failure" to failure, @@ -1601,7 +1667,7 @@ class Account(client: Client) : Service(client) { } } else -> { - apiQuery.add("${it.key}=${it.value.toString()}") + apiQuery.add("${it.key}=${it.value.toString()}") } } } @@ -1627,7 +1693,7 @@ class Account(client: Client) : Service(client) { .domain(Uri.parse(client.endpoint).host!!) .httpOnly() .build() - + client.http.cookieJar.saveFromResponse( client.endpoint.toHttpUrl(), listOf(cookie) @@ -1635,7 +1701,6 @@ class Account(client: Client) : Service(client) { } } - /** * Use this endpoint to create a session from token. Provide the **userId** and **secret** parameters from the successful response of authentication flows initiated by token creation. For example, magic URL and phone login. * @@ -1651,9 +1716,7 @@ class Account(client: Client) : Service(client) { userId: String, secret: String, ): io.appwrite.models.Session { - val apiPath = ("/account/sessions/phone" - ) - + val apiPath = "/account/sessions/phone" val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -1677,7 +1740,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to create a session from token. Provide the **userId** and **secret** parameters from the successful response of authentication flows initiated by token creation. For example, magic URL and phone login. * @@ -1689,9 +1751,7 @@ class Account(client: Client) : Service(client) { userId: String, secret: String, ): io.appwrite.models.Session { - val apiPath = ("/account/sessions/token" - ) - + val apiPath = "/account/sessions/token" val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -1715,7 +1775,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to get a logged in user's session using a Session ID. Inputting 'current' will return the current session being used. * @@ -1728,9 +1787,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/sessions/{sessionId}" .replace("{sessionId}", sessionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -1749,7 +1806,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to extend a session's length. Extending a session is useful when session expiry is short. If the session was created using an OAuth provider, this endpoint refreshes the access token from the provider. * @@ -1762,9 +1818,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/sessions/{sessionId}" .replace("{sessionId}", sessionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1784,7 +1838,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Logout the user. Use 'current' as the session ID to logout on this device, use a session ID to logout on another device. If you're looking to logout the user on all devices, use [Delete Sessions](https://appwrite.io/docs/references/cloud/client-web/account#deleteSessions) instead. * @@ -1797,9 +1850,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/sessions/{sessionId}" .replace("{sessionId}", sessionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1813,7 +1864,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Block the currently logged in user account. Behind the scene, the user record is not deleted but permanently blocked from any access. To completely delete a user, use the Users API instead. * @@ -1822,11 +1872,8 @@ class Account(client: Client) : Service(client) { suspend fun updateStatus( nestedType: Class, ): io.appwrite.models.User { - val apiPath = ("/account/status" - ) - - val apiParams = mutableMapOf( - ) + val apiPath = "/account/status" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1852,8 +1899,7 @@ class Account(client: Client) : Service(client) { * @return [io.appwrite.models.User] */ @Throws(AppwriteException::class) - suspend fun updateStatus( - ): io.appwrite.models.User> = updateStatus( + suspend fun updateStatus(): io.appwrite.models.User> = updateStatus( nestedType = classOf(), ) @@ -1871,9 +1917,7 @@ class Account(client: Client) : Service(client) { identifier: String, providerId: String? = null, ): io.appwrite.models.Target { - val apiPath = ("/account/targets/push" - ) - + val apiPath = "/account/targets/push" val apiParams = mutableMapOf( "targetId" to targetId, "identifier" to identifier, @@ -1898,7 +1942,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Update the currently logged in user's push notification target. You can modify the target's identifier (device token) and provider ID (token, email, phone etc.). The target must exist and belong to the current user. If you change the provider ID, notifications will be sent through the new messaging provider instead. * @@ -1913,7 +1956,6 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/targets/{targetId}/push" .replace("{targetId}", targetId) ) - val apiParams = mutableMapOf( "identifier" to identifier, ) @@ -1936,7 +1978,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Delete a push notification target for the currently logged in user. After deletion, the device will no longer receive push notifications. The target must exist and belong to the current user. * @@ -1949,9 +1990,7 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/targets/{targetId}/push" .replace("{targetId}", targetId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1965,12 +2004,11 @@ class Account(client: Client) : Service(client) { ) } - /** * Sends the user an email with a secret key for creating a session. If the email address has never been used, a **new account is created** using the provided `userId`. Otherwise, if the email address is already attached to an account, the **user ID is ignored**. Then, the user will receive an email with the one-time password. Use the returned user ID and secret and submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes. - * + * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). - * + * * * @param userId User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored. * @param email User email. @@ -1983,9 +2021,7 @@ class Account(client: Client) : Service(client) { email: String, phrase: Boolean? = null, ): io.appwrite.models.Token { - val apiPath = ("/account/tokens/email" - ) - + val apiPath = "/account/tokens/email" val apiParams = mutableMapOf( "userId" to userId, "email" to email, @@ -2010,12 +2046,11 @@ class Account(client: Client) : Service(client) { ) } - /** * Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. - * + * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). - * + * * * @param userId Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored. * @param email User email. @@ -2030,9 +2065,7 @@ class Account(client: Client) : Service(client) { url: String? = null, phrase: Boolean? = null, ): io.appwrite.models.Token { - val apiPath = ("/account/tokens/magic-url" - ) - + val apiPath = "/account/tokens/magic-url" val apiParams = mutableMapOf( "userId" to userId, "email" to email, @@ -2058,15 +2091,14 @@ class Account(client: Client) : Service(client) { ) } - /** - * Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. - * + * Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. + * * If authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint. - * + * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -2082,7 +2114,6 @@ class Account(client: Client) : Service(client) { val apiPath = ("/account/tokens/oauth2/{provider}" .replace("{provider}", provider.value) ) - val apiParams = mutableMapOf( "success" to success, "failure" to failure, @@ -2101,7 +2132,7 @@ class Account(client: Client) : Service(client) { } } else -> { - apiQuery.add("${it.key}=${it.value.toString()}") + apiQuery.add("${it.key}=${it.value.toString()}") } } } @@ -2127,7 +2158,7 @@ class Account(client: Client) : Service(client) { .domain(Uri.parse(client.endpoint).host!!) .httpOnly() .build() - + client.http.cookieJar.saveFromResponse( client.endpoint.toHttpUrl(), listOf(cookie) @@ -2135,10 +2166,9 @@ class Account(client: Client) : Service(client) { } } - /** * Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes. - * + * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * @param userId Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. If the phone number has never been used, a new account is created using the provided userId. Otherwise, if the phone number is already attached to an account, the user ID is ignored. @@ -2149,9 +2179,7 @@ class Account(client: Client) : Service(client) { userId: String, phone: String, ): io.appwrite.models.Token { - val apiPath = ("/account/tokens/phone" - ) - + val apiPath = "/account/tokens/phone" val apiParams = mutableMapOf( "userId" to userId, "phone" to phone, @@ -2175,12 +2203,11 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https://appwrite.io/docs/references/cloud/client-web/account#updateVerification). The verification link sent to the user's email address is valid for 7 days. - * + * * Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. - * + * * * @param url URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @return [io.appwrite.models.Token] @@ -2188,9 +2215,7 @@ class Account(client: Client) : Service(client) { suspend fun createEmailVerification( url: String, ): io.appwrite.models.Token { - val apiPath = ("/account/verifications/email" - ) - + val apiPath = "/account/verifications/email" val apiParams = mutableMapOf( "url" to url, ) @@ -2213,12 +2238,11 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https://appwrite.io/docs/references/cloud/client-web/account#updateVerification). The verification link sent to the user's email address is valid for 7 days. - * + * * Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. - * + * * * @param url URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @return [io.appwrite.models.Token] @@ -2230,9 +2254,7 @@ class Account(client: Client) : Service(client) { suspend fun createVerification( url: String, ): io.appwrite.models.Token { - val apiPath = ("/account/verifications/email" - ) - + val apiPath = "/account/verifications/email" val apiParams = mutableMapOf( "url" to url, ) @@ -2255,7 +2277,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code. * @@ -2267,9 +2288,7 @@ class Account(client: Client) : Service(client) { userId: String, secret: String, ): io.appwrite.models.Token { - val apiPath = ("/account/verifications/email" - ) - + val apiPath = "/account/verifications/email" val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -2293,7 +2312,6 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code. * @@ -2309,9 +2327,7 @@ class Account(client: Client) : Service(client) { userId: String, secret: String, ): io.appwrite.models.Token { - val apiPath = ("/account/verifications/email" - ) - + val apiPath = "/account/verifications/email" val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -2335,19 +2351,14 @@ class Account(client: Client) : Service(client) { ) } - /** * Use this endpoint to send a verification SMS to the currently logged in user. This endpoint is meant for use after updating a user's phone number using the [accountUpdatePhone](https://appwrite.io/docs/references/cloud/client-web/account#updatePhone) endpoint. Learn more about how to [complete the verification process](https://appwrite.io/docs/references/cloud/client-web/account#updatePhoneVerification). The verification code sent to the user's phone number is valid for 15 minutes. * * @return [io.appwrite.models.Token] */ - suspend fun createPhoneVerification( - ): io.appwrite.models.Token { - val apiPath = ("/account/verifications/phone" - ) - - val apiParams = mutableMapOf( - ) + suspend fun createPhoneVerification(): io.appwrite.models.Token { + val apiPath = "/account/verifications/phone" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -2367,7 +2378,6 @@ class Account(client: Client) : Service(client) { ) } - /** * 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 verify the user email ownership. If confirmed this route will return a 200 status code. * @@ -2379,9 +2389,7 @@ class Account(client: Client) : Service(client) { userId: String, secret: String, ): io.appwrite.models.Token { - val apiPath = ("/account/verifications/phone" - ) - + val apiPath = "/account/verifications/phone" val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -2404,6 +2412,4 @@ class Account(client: Client) : Service(client) { converter, ) } - - } diff --git a/library/src/main/java/io/appwrite/services/Avatars.kt b/library/src/main/java/io/appwrite/services/Avatars.kt index 3a01f1e3..7541466c 100644 --- a/library/src/main/java/io/appwrite/services/Avatars.kt +++ b/library/src/main/java/io/appwrite/services/Avatars.kt @@ -3,22 +3,20 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars. - */ + * The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars. */ class Avatars(client: Client) : Service(client) { - /** * You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET /account/sessions](https://appwrite.io/docs/references/cloud/client-web/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings. - * + * * When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px. * * @param code Browser Code. @@ -37,28 +35,29 @@ class Avatars(client: Client) : Service(client) { val apiPath = ("/avatars/browsers/{code}" .replace("{code}", code.value) ) - val apiParams = mutableMapOf( "width" to width, "height" to height, "quality" to quality, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/png", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings. - * + * * When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px. - * + * * * @param code Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, unionpay, visa, mir, maestro, rupay. * @param width Image width. Pass an integer between 0 to 2000. Defaults to 100. @@ -76,26 +75,27 @@ class Avatars(client: Client) : Service(client) { val apiPath = ("/avatars/credit-cards/{code}" .replace("{code}", code.value) ) - val apiParams = mutableMapOf( "width" to width, "height" to height, "quality" to quality, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/png", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL. - * + * * This endpoint does not follow HTTP redirects. * * @param url Website URL which you want to fetch the favicon from. @@ -104,28 +104,28 @@ class Avatars(client: Client) : Service(client) { suspend fun getFavicon( url: String, ): ByteArray { - val apiPath = ("/avatars/favicon" - ) - + val apiPath = "/avatars/favicon" val apiParams = mutableMapOf( "url" to url, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/*", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) standard. - * + * * When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px. - * + * * * @param code Country Code. ISO Alpha-2 country code format. * @param width Image width. Pass an integer between 0 to 2000. Defaults to 100. @@ -143,28 +143,29 @@ class Avatars(client: Client) : Service(client) { val apiPath = ("/avatars/flags/{code}" .replace("{code}", code.value) ) - val apiParams = mutableMapOf( "width" to width, "height" to height, "quality" to quality, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/png", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol. - * + * * When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an 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 url Image URL which you want to crop. @@ -178,32 +179,32 @@ class Avatars(client: Client) : Service(client) { width: Long? = null, height: Long? = null, ): ByteArray { - val apiPath = ("/avatars/image" - ) - + val apiPath = "/avatars/image" val apiParams = mutableMapOf( "url" to url, "width" to width, "height" to height, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/*", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned. - * + * * You can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials. - * + * * When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px. - * + * * * @param name Full Name. When empty, current user name or email will be used. Max length: 128 chars. * @param width Image width. Pass an integer between 0 to 2000. Defaults to 100. @@ -218,29 +219,68 @@ class Avatars(client: Client) : Service(client) { height: Long? = null, background: String? = null, ): ByteArray { - val apiPath = ("/avatars/initials" - ) - + val apiPath = "/avatars/initials" val apiParams = mutableMapOf( "name" to name, "width" to width, "height" to height, "background" to background, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/png", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } + /** + * Returns the best available profile photo for the currently authenticated user. The endpoint tries each source in priority order and returns the first successful result: Gravatar, Libavatar, Appwrite Initials, built-in static fallback file. + * + * @param width Output image width in pixels. Pass an integer between 0 and 2000. Defaults to 256. + * @param height Output image height in pixels. Pass an integer between 0 and 2000. Defaults to 256. + * @param quality Output image quality between 0 and 100. Defaults to 100. + * @param output Output image format. Defaults to 'png'. + * @param rating Maximum image rating to fetch from Gravatar/Libravatar. Defaults to 'g'. + * @return [ByteArray] + */ + @JvmOverloads + suspend fun getPhoto( + width: Long? = null, + height: Long? = null, + quality: Long? = null, + output: String? = null, + rating: String? = null, + ): ByteArray { + val apiPath = "/avatars/photo" + val apiParams = mutableMapOf( + "width" to width, + "height" to height, + "quality" to quality, + "output" to output, + "rating" to rating, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/*", + ) + return client.call( + "GET", + apiPath, + headers = apiHeaders, + params = apiParams, + responseType = ByteArray::class.java + ) + } /** * Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image. - * + * * * @param text Plain text to be converted to QR code image. * @param size QR code size. Pass an integer between 1 to 1000. Defaults to 400. @@ -255,31 +295,31 @@ class Avatars(client: Client) : Service(client) { margin: Long? = null, download: Boolean? = null, ): ByteArray { - val apiPath = ("/avatars/qr" - ) - + val apiPath = "/avatars/qr" val apiParams = mutableMapOf( "text" to text, "size" to size, "margin" to margin, "download" to download, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/png", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * Use this endpoint to capture a screenshot of any website URL. This endpoint uses a headless browser to render the webpage and capture it as an image. - * + * * You can configure the browser viewport size, theme, user agent, geolocation, permissions, and more. Capture either just the viewport or the full page scroll. - * + * * When width and height are specified, the image is resized accordingly. If both dimensions are 0, the API provides an image at original size. If dimensions are not specified, the default viewport size is 1280x720px. * * @param url Website URL which you want to capture. @@ -307,7 +347,7 @@ class Avatars(client: Client) : Service(client) { @JvmOverloads suspend fun getScreenshot( url: String, - headers: Any? = null, + headers: Map? = null, viewportWidth: Long? = null, viewportHeight: Long? = null, scale: Double? = null, @@ -327,9 +367,7 @@ class Avatars(client: Client) : Service(client) { quality: Long? = null, output: io.appwrite.enums.ImageFormat? = null, ): ByteArray { - val apiPath = ("/avatars/screenshots" - ) - + val apiPath = "/avatars/screenshots" val apiParams = mutableMapOf( "url" to url, "headers" to headers, @@ -351,16 +389,17 @@ class Avatars(client: Client) : Service(client) { "height" to height, "quality" to quality, "output" to output, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/png", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - - } diff --git a/library/src/main/java/io/appwrite/services/Databases.kt b/library/src/main/java/io/appwrite/services/Databases.kt index 9fba03a8..a1dda292 100644 --- a/library/src/main/java/io/appwrite/services/Databases.kt +++ b/library/src/main/java/io/appwrite/services/Databases.kt @@ -3,17 +3,15 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * The Databases service allows you to create structured collections of documents, query and filter lists of documents - */ + * The Databases service allows you to create structured collections of documents, query and filter lists of documents */ class Databases(client: Client) : Service(client) { - /** * List transactions across all databases. * @@ -28,9 +26,7 @@ class Databases(client: Client) : Service(client) { suspend fun listTransactions( queries: List? = null, ): io.appwrite.models.TransactionList { - val apiPath = ("/databases/transactions" - ) - + val apiPath = "/databases/transactions" val apiParams = mutableMapOf( "queries" to queries, ) @@ -52,7 +48,6 @@ class Databases(client: Client) : Service(client) { ) } - /** * Create a new transaction. * @@ -67,9 +62,7 @@ class Databases(client: Client) : Service(client) { suspend fun createTransaction( ttl: Long? = null, ): io.appwrite.models.Transaction { - val apiPath = ("/databases/transactions" - ) - + val apiPath = "/databases/transactions" val apiParams = mutableMapOf( "ttl" to ttl, ) @@ -92,7 +85,6 @@ class Databases(client: Client) : Service(client) { ) } - /** * Get a transaction by its unique ID. * @@ -109,9 +101,7 @@ class Databases(client: Client) : Service(client) { val apiPath = ("/databases/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -130,7 +120,6 @@ class Databases(client: Client) : Service(client) { ) } - /** * Update a transaction, to either commit or roll back its operations. * @@ -152,7 +141,6 @@ class Databases(client: Client) : Service(client) { val apiPath = ("/databases/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) - val apiParams = mutableMapOf( "commit" to commit, "rollback" to rollback, @@ -176,7 +164,6 @@ class Databases(client: Client) : Service(client) { ) } - /** * Delete a transaction by its unique ID. * @@ -193,9 +180,7 @@ class Databases(client: Client) : Service(client) { val apiPath = ("/databases/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -209,7 +194,6 @@ class Databases(client: Client) : Service(client) { ) } - /** * Create multiple operations in a single transaction. * @@ -224,12 +208,11 @@ class Databases(client: Client) : Service(client) { @JvmOverloads suspend fun createOperations( transactionId: String, - operations: List? = null, + operations: List>? = null, ): io.appwrite.models.Transaction { val apiPath = ("/databases/transactions/{transactionId}/operations" .replace("{transactionId}", transactionId) ) - val apiParams = mutableMapOf( "operations" to operations, ) @@ -252,7 +235,6 @@ class Databases(client: Client) : Service(client) { ) } - /** * Get a list of all the user's documents in a given collection. You can use the query params to filter your results. * @@ -282,7 +264,6 @@ class Databases(client: Client) : Service(client) { .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) ) - val apiParams = mutableMapOf( "queries" to queries, "transactionId" to transactionId, @@ -361,7 +342,7 @@ class Databases(client: Client) : Service(client) { databaseId: String, collectionId: String, documentId: String, - data: Any, + data: Map, permissions: List? = null, transactionId: String? = null, nestedType: Class, @@ -370,7 +351,6 @@ class Databases(client: Client) : Service(client) { .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) ) - val apiParams = mutableMapOf( "documentId" to documentId, "data" to data, @@ -417,7 +397,7 @@ class Databases(client: Client) : Service(client) { databaseId: String, collectionId: String, documentId: String, - data: Any, + data: Map, permissions: List? = null, transactionId: String? = null, ): io.appwrite.models.Document> = createDocument( @@ -458,7 +438,6 @@ class Databases(client: Client) : Service(client) { .replace("{collectionId}", collectionId) .replace("{documentId}", documentId) ) - val apiParams = mutableMapOf( "queries" to queries, "transactionId" to transactionId, @@ -532,7 +511,7 @@ class Databases(client: Client) : Service(client) { databaseId: String, collectionId: String, documentId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, nestedType: Class, @@ -542,7 +521,6 @@ class Databases(client: Client) : Service(client) { .replace("{collectionId}", collectionId) .replace("{documentId}", documentId) ) - val apiParams = mutableMapOf( "data" to data, "permissions" to permissions, @@ -588,7 +566,7 @@ class Databases(client: Client) : Service(client) { databaseId: String, collectionId: String, documentId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, ): io.appwrite.models.Document> = upsertDocument( @@ -621,7 +599,7 @@ class Databases(client: Client) : Service(client) { databaseId: String, collectionId: String, documentId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, nestedType: Class, @@ -631,7 +609,6 @@ class Databases(client: Client) : Service(client) { .replace("{collectionId}", collectionId) .replace("{documentId}", documentId) ) - val apiParams = mutableMapOf( "data" to data, "permissions" to permissions, @@ -677,7 +654,7 @@ class Databases(client: Client) : Service(client) { databaseId: String, collectionId: String, documentId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, ): io.appwrite.models.Document> = updateDocument( @@ -715,7 +692,6 @@ class Databases(client: Client) : Service(client) { .replace("{collectionId}", collectionId) .replace("{documentId}", documentId) ) - val apiParams = mutableMapOf( "transactionId" to transactionId, ) @@ -732,7 +708,6 @@ class Databases(client: Client) : Service(client) { ) } - /** * Decrement a specific attribute of a document by a given value. * @@ -766,7 +741,6 @@ class Databases(client: Client) : Service(client) { .replace("{documentId}", documentId) .replace("{attribute}", attribute) ) - val apiParams = mutableMapOf( "value" to value, "min" to min, @@ -861,7 +835,6 @@ class Databases(client: Client) : Service(client) { .replace("{documentId}", documentId) .replace("{attribute}", attribute) ) - val apiParams = mutableMapOf( "value" to value, "max" to max, @@ -922,5 +895,4 @@ class Databases(client: Client) : Service(client) { transactionId, nestedType = classOf(), ) - } diff --git a/library/src/main/java/io/appwrite/services/Functions.kt b/library/src/main/java/io/appwrite/services/Functions.kt index f56ea7f6..b6da2d17 100644 --- a/library/src/main/java/io/appwrite/services/Functions.kt +++ b/library/src/main/java/io/appwrite/services/Functions.kt @@ -3,17 +3,15 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * The Functions Service allows you view, create and manage your Cloud Functions. - */ + * The Functions Service allows you view, create and manage your Cloud Functions. */ class Functions(client: Client) : Service(client) { - /** * Get a list of all the current user function execution logs. You can use the query params to filter your results. * @@ -31,7 +29,6 @@ class Functions(client: Client) : Service(client) { val apiPath = ("/functions/{functionId}/executions" .replace("{functionId}", functionId) ) - val apiParams = mutableMapOf( "queries" to queries, "total" to total, @@ -54,7 +51,6 @@ class Functions(client: Client) : Service(client) { ) } - /** * Trigger a function execution. The returned object will return you the current execution status. You can ping the `Get Execution` endpoint to get updates on the current execution status. Once this endpoint is called, your function execution process will start asynchronously. * @@ -74,13 +70,12 @@ class Functions(client: Client) : Service(client) { async: Boolean? = null, path: String? = null, method: io.appwrite.enums.ExecutionMethod? = null, - headers: Any? = null, + headers: Map? = null, scheduledAt: String? = null, ): io.appwrite.models.Execution { val apiPath = ("/functions/{functionId}/executions" .replace("{functionId}", functionId) ) - val apiParams = mutableMapOf( "body" to body, "async" to async, @@ -108,7 +103,6 @@ class Functions(client: Client) : Service(client) { ) } - /** * Get a function execution log by its unique ID. * @@ -124,9 +118,7 @@ class Functions(client: Client) : Service(client) { .replace("{functionId}", functionId) .replace("{executionId}", executionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -144,6 +136,4 @@ class Functions(client: Client) : Service(client) { converter, ) } - - } diff --git a/library/src/main/java/io/appwrite/services/Graphql.kt b/library/src/main/java/io/appwrite/services/Graphql.kt index cef1cc6b..c1a776d9 100644 --- a/library/src/main/java/io/appwrite/services/Graphql.kt +++ b/library/src/main/java/io/appwrite/services/Graphql.kt @@ -3,17 +3,15 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * The GraphQL API allows you to query and mutate your Appwrite server using GraphQL. - */ + * The GraphQL API allows you to query and mutate your Appwrite server using GraphQL. */ class Graphql(client: Client) : Service(client) { - /** * Execute a GraphQL mutation. * @@ -21,11 +19,9 @@ class Graphql(client: Client) : Service(client) { * @return [Any] */ suspend fun query( - query: Any, + query: Map, ): Any { - val apiPath = ("/graphql" - ) - + val apiPath = "/graphql" val apiParams = mutableMapOf( "query" to query, ) @@ -48,7 +44,6 @@ class Graphql(client: Client) : Service(client) { ) } - /** * Execute a GraphQL mutation. * @@ -56,11 +51,9 @@ class Graphql(client: Client) : Service(client) { * @return [Any] */ suspend fun mutation( - query: Any, + query: Map, ): Any { - val apiPath = ("/graphql/mutation" - ) - + val apiPath = "/graphql/mutation" val apiParams = mutableMapOf( "query" to query, ) @@ -82,6 +75,4 @@ class Graphql(client: Client) : Service(client) { converter, ) } - - } diff --git a/library/src/main/java/io/appwrite/services/Locale.kt b/library/src/main/java/io/appwrite/services/Locale.kt index a0434aff..9c08a68d 100644 --- a/library/src/main/java/io/appwrite/services/Locale.kt +++ b/library/src/main/java/io/appwrite/services/Locale.kt @@ -3,31 +3,25 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * The Locale service allows you to customize your app based on your users' location. - */ + * The Locale service allows you to customize your app based on your users' location. */ class Locale(client: Client) : Service(client) { - /** * Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language. - * + * * ([IP Geolocation by DB-IP](https://db-ip.com)) * * @return [io.appwrite.models.Locale] */ - suspend fun get( - ): io.appwrite.models.Locale { - val apiPath = ("/locale" - ) - - val apiParams = mutableMapOf( - ) + suspend fun get(): io.appwrite.models.Locale { + val apiPath = "/locale" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -46,19 +40,14 @@ class Locale(client: Client) : Service(client) { ) } - /** * List of all locale codes in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). * * @return [io.appwrite.models.LocaleCodeList] */ - suspend fun listCodes( - ): io.appwrite.models.LocaleCodeList { - val apiPath = ("/locale/codes" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listCodes(): io.appwrite.models.LocaleCodeList { + val apiPath = "/locale/codes" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -77,19 +66,14 @@ class Locale(client: Client) : Service(client) { ) } - /** * List of all continents. You can use the locale header to get the data in a supported language. * * @return [io.appwrite.models.ContinentList] */ - suspend fun listContinents( - ): io.appwrite.models.ContinentList { - val apiPath = ("/locale/continents" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listContinents(): io.appwrite.models.ContinentList { + val apiPath = "/locale/continents" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -108,19 +92,14 @@ class Locale(client: Client) : Service(client) { ) } - /** * List of all countries. You can use the locale header to get the data in a supported language. * * @return [io.appwrite.models.CountryList] */ - suspend fun listCountries( - ): io.appwrite.models.CountryList { - val apiPath = ("/locale/countries" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listCountries(): io.appwrite.models.CountryList { + val apiPath = "/locale/countries" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -139,19 +118,14 @@ class Locale(client: Client) : Service(client) { ) } - /** * List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language. * * @return [io.appwrite.models.CountryList] */ - suspend fun listCountriesEU( - ): io.appwrite.models.CountryList { - val apiPath = ("/locale/countries/eu" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listCountriesEU(): io.appwrite.models.CountryList { + val apiPath = "/locale/countries/eu" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -170,19 +144,14 @@ class Locale(client: Client) : Service(client) { ) } - /** * List of all countries phone codes. You can use the locale header to get the data in a supported language. * * @return [io.appwrite.models.PhoneList] */ - suspend fun listCountriesPhones( - ): io.appwrite.models.PhoneList { - val apiPath = ("/locale/countries/phones" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listCountriesPhones(): io.appwrite.models.PhoneList { + val apiPath = "/locale/countries/phones" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -201,19 +170,14 @@ class Locale(client: Client) : Service(client) { ) } - /** * List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language. * * @return [io.appwrite.models.CurrencyList] */ - suspend fun listCurrencies( - ): io.appwrite.models.CurrencyList { - val apiPath = ("/locale/currencies" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listCurrencies(): io.appwrite.models.CurrencyList { + val apiPath = "/locale/currencies" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -232,19 +196,14 @@ class Locale(client: Client) : Service(client) { ) } - /** * List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language. * * @return [io.appwrite.models.LanguageList] */ - suspend fun listLanguages( - ): io.appwrite.models.LanguageList { - val apiPath = ("/locale/languages" - ) - - val apiParams = mutableMapOf( - ) + suspend fun listLanguages(): io.appwrite.models.LanguageList { + val apiPath = "/locale/languages" + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -262,6 +221,4 @@ class Locale(client: Client) : Service(client) { converter, ) } - - } diff --git a/library/src/main/java/io/appwrite/services/Messaging.kt b/library/src/main/java/io/appwrite/services/Messaging.kt index 7a219a44..85b03fb2 100644 --- a/library/src/main/java/io/appwrite/services/Messaging.kt +++ b/library/src/main/java/io/appwrite/services/Messaging.kt @@ -3,17 +3,15 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.). - */ + * The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.). */ class Messaging(client: Client) : Service(client) { - /** * Create a new subscriber. * @@ -30,7 +28,6 @@ class Messaging(client: Client) : Service(client) { val apiPath = ("/messaging/topics/{topicId}/subscribers" .replace("{topicId}", topicId) ) - val apiParams = mutableMapOf( "subscriberId" to subscriberId, "targetId" to targetId, @@ -54,7 +51,6 @@ class Messaging(client: Client) : Service(client) { ) } - /** * Delete a subscriber by its unique ID. * @@ -70,9 +66,7 @@ class Messaging(client: Client) : Service(client) { .replace("{topicId}", topicId) .replace("{subscriberId}", subscriberId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -85,6 +79,4 @@ class Messaging(client: Client) : Service(client) { responseType = Any::class.java, ) } - - } diff --git a/library/src/main/java/io/appwrite/services/Organization.kt b/library/src/main/java/io/appwrite/services/Organization.kt new file mode 100644 index 00000000..64b343c2 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Organization.kt @@ -0,0 +1,180 @@ +package io.appwrite.services + +import android.net.Uri +import io.appwrite.Client +import io.appwrite.Service +import io.appwrite.exceptions.AppwriteException +import io.appwrite.extensions.classOf +import io.appwrite.models.* +import okhttp3.Cookie +import java.io.File + +/** + * The Organization service allows you to manage organization-level projects. */ +class Organization(client: Client) : Service(client) { + /** + * List app installations on the organization. Any organization member can read installations. + * + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppInstallationList] + */ + @JvmOverloads + suspend fun listInstallations( + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppInstallationList { + val apiPath = "/organization/installations" + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallationList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallationList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallationList::class.java, + converter, + ) + } + + /** + * Install an app on the organization. Only organization members with the owner role can install apps. The installation is granted the scopes the app currently requests. + * + * @param appId Application unique ID. + * @param authorizationDetails Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. The Appwrite Console stores authorized project IDs here. + * @return [io.appwrite.models.AppInstallation] + */ + @JvmOverloads + suspend fun createInstallation( + appId: String, + authorizationDetails: String? = null, + ): io.appwrite.models.AppInstallation { + val apiPath = "/organization/installations" + val apiParams = mutableMapOf( + "appId" to appId, + "authorizationDetails" to authorizationDetails, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, + ) + } + + /** + * Get an app installation on the organization by its unique ID. Any organization member can read installations. + * + * @param installationId Installation unique ID. + * @return [io.appwrite.models.AppInstallation] + */ + suspend fun getInstallation( + installationId: String, + ): io.appwrite.models.AppInstallation { + val apiPath = ("/organization/installations/{installationId}" + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, + ) + } + + /** + * Update an app installation on the organization. Only organization members with the owner role can update installations. The installation's granted scopes are refreshed to the scopes the app currently requests; previously issued installation access tokens are revoked. + * + * @param installationId Installation unique ID. + * @param authorizationDetails Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. Omit to keep the current value. + * @return [io.appwrite.models.AppInstallation] + */ + @JvmOverloads + suspend fun updateInstallation( + installationId: String, + authorizationDetails: String? = null, + ): io.appwrite.models.AppInstallation { + val apiPath = ("/organization/installations/{installationId}" + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf( + "authorizationDetails" to authorizationDetails, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, + ) + } + + /** + * Uninstall an app from the organization by its installation ID. Only organization members with the owner role can remove installations. Previously issued installation access tokens are revoked. + * + * @param installationId Installation unique ID. + * @return [Any] + */ + suspend fun deleteInstallation( + installationId: String, + ): Any { + val apiPath = ("/organization/installations/{installationId}" + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } +} diff --git a/library/src/main/java/io/appwrite/services/Presences.kt b/library/src/main/java/io/appwrite/services/Presences.kt index 2c9147df..1d22d96d 100644 --- a/library/src/main/java/io/appwrite/services/Presences.kt +++ b/library/src/main/java/io/appwrite/services/Presences.kt @@ -3,20 +3,18 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * - */ + * The Presences service allows you to track and manage real-time user presence in your project. */ class Presences(client: Client) : Service(client) { - /** * List presence logs. Expired entries are filtered out automatically. - * + * * * @param queries Array of query strings generated using the Query class provided by the SDK. * @param total When set to false, the total count returned will be 0 and will not be calculated. @@ -29,9 +27,7 @@ class Presences(client: Client) : Service(client) { total: Boolean? = null, ttl: Long? = null, ): io.appwrite.models.PresenceList { - val apiPath = ("/presences" - ) - + val apiPath = "/presences" val apiParams = mutableMapOf( "queries" to queries, "total" to total, @@ -55,10 +51,9 @@ class Presences(client: Client) : Service(client) { ) } - /** * Get a presence log by its unique ID. Entries whose `expiresAt` is in the past are treated as not found. - * + * * * @param presenceId Presence unique ID. * @return [io.appwrite.models.Presence] @@ -69,9 +64,7 @@ class Presences(client: Client) : Service(client) { val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -90,10 +83,9 @@ class Presences(client: Client) : Service(client) { ) } - /** * Create or update a presence log by its user ID. - * + * * * @param presenceId Presence unique ID. * @param status Presence status. @@ -108,12 +100,11 @@ class Presences(client: Client) : Service(client) { status: String, permissions: List? = null, expiresAt: String? = null, - metadata: Any? = null, + metadata: Map? = null, ): io.appwrite.models.Presence { val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) - val apiParams = mutableMapOf( "status" to status, "permissions" to permissions, @@ -139,10 +130,9 @@ class Presences(client: Client) : Service(client) { ) } - /** * Update a presence log by its unique ID. Using the patch method you can pass only specific fields that will get updated. - * + * * * @param presenceId Presence unique ID. * @param status Presence status. @@ -157,14 +147,13 @@ class Presences(client: Client) : Service(client) { presenceId: String, status: String? = null, expiresAt: String? = null, - metadata: Any? = null, + metadata: Map? = null, permissions: List? = null, purge: Boolean? = null, ): io.appwrite.models.Presence { val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) - val apiParams = mutableMapOf( "status" to status, "expiresAt" to expiresAt, @@ -191,10 +180,9 @@ class Presences(client: Client) : Service(client) { ) } - /** * Delete a presence log by its unique ID. - * + * * * @param presenceId Presence unique ID. * @return [Any] @@ -205,9 +193,7 @@ class Presences(client: Client) : Service(client) { val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -220,6 +206,4 @@ class Presences(client: Client) : Service(client) { responseType = Any::class.java, ) } - - } diff --git a/library/src/main/java/io/appwrite/services/Realtime.kt b/library/src/main/java/io/appwrite/services/Realtime.kt index bb1233ab..60e6f41a 100644 --- a/library/src/main/java/io/appwrite/services/Realtime.kt +++ b/library/src/main/java/io/appwrite/services/Realtime.kt @@ -1,10 +1,11 @@ package io.appwrite.services -import io.appwrite.Service -import io.appwrite.Client +import android.util.Log import io.appwrite.Channel +import io.appwrite.Client import io.appwrite.ID import io.appwrite.Query +import io.appwrite.Service import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.forEachAsync import io.appwrite.extensions.fromJson @@ -19,7 +20,6 @@ import okhttp3.WebSocket import okhttp3.WebSocketListener import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger -import android.util.Log import kotlin.coroutines.CoroutineContext class Realtime(client: Client) : Service(client), CoroutineScope { @@ -407,7 +407,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { } } } - + private fun handleResponseConnected(message: RealtimeResponse) { if (message.data == null) return @@ -427,7 +427,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { private suspend fun handleResponseEvent(message: RealtimeResponse) { val messageData = message.data?.jsonCast>() ?: return val event = messageData.jsonCast>() - + if (event.channels.isEmpty()) { return } @@ -493,4 +493,4 @@ class Realtime(client: Client) : Service(client), CoroutineScope { t.printStackTrace() } } -} \ No newline at end of file +} diff --git a/library/src/main/java/io/appwrite/services/Storage.kt b/library/src/main/java/io/appwrite/services/Storage.kt index 7e95b527..1cf0810c 100644 --- a/library/src/main/java/io/appwrite/services/Storage.kt +++ b/library/src/main/java/io/appwrite/services/Storage.kt @@ -3,24 +3,22 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Storage service allows you to manage your project files. - */ + * The Storage service allows you to manage your project files. */ class Storage(client: Client) : Service(client) { - /** * Get a list of all the user files. You can use the query params to filter your results. * * @param bucketId Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket). - * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, signature, mimeType, sizeOriginal, chunksTotal, chunksUploaded + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, folder, signature, mimeType, sizeOriginal, chunksTotal, chunksUploaded * @param search Search term to filter your list results. Max length: 256 chars. * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.FileList] @@ -35,7 +33,6 @@ class Storage(client: Client) : Service(client) { val apiPath = ("/storage/buckets/{bucketId}/files" .replace("{bucketId}", bucketId) ) - val apiParams = mutableMapOf( "queries" to queries, "search" to search, @@ -59,21 +56,21 @@ class Storage(client: Client) : Service(client) { ) } - /** * Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https://appwrite.io/docs/server/storage#storageCreateBucket) API or directly from your Appwrite console. - * + * * Larger files should be uploaded using multiple requests with the [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes. - * + * * When the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one. - * + * * If you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally. - * + * * * @param bucketId Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket). * @param fileId File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param file Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file). * @param permissions An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param folder Virtual folder to place the file in, for example "photos/2026". Nest folders with `/`. Defaults to the bucket root. * @return [io.appwrite.models.File] */ @JvmOverloads @@ -82,16 +79,17 @@ class Storage(client: Client) : Service(client) { fileId: String, file: InputFile, permissions: List? = null, + folder: String? = null, onProgress: ((UploadProgress) -> Unit)? = null ): io.appwrite.models.File { val apiPath = ("/storage/buckets/{bucketId}/files" .replace("{bucketId}", bucketId) ) - val apiParams = mutableMapOf( "fileId" to fileId, "file" to file, "permissions" to permissions, + "folder" to folder, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), @@ -102,7 +100,7 @@ class Storage(client: Client) : Service(client) { @Suppress("UNCHECKED_CAST") io.appwrite.models.File.from(map = it as Map) } - val idParamName: String? = "fileId" + val idParamName: String? = "fileId" val paramName = "file" return client.chunkedUpload( apiPath, @@ -116,7 +114,6 @@ class Storage(client: Client) : Service(client) { ) } - /** * Get a file by its unique ID. This endpoint response returns a JSON object with the file metadata. * @@ -132,9 +129,7 @@ class Storage(client: Client) : Service(client) { .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -153,7 +148,6 @@ class Storage(client: Client) : Service(client) { ) } - /** * Update a file by its unique ID. Only users with write permissions have access to update this resource. * @@ -174,7 +168,6 @@ class Storage(client: Client) : Service(client) { .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) ) - val apiParams = mutableMapOf( "name" to name, "permissions" to permissions, @@ -198,7 +191,6 @@ class Storage(client: Client) : Service(client) { ) } - /** * Delete a file by its unique ID. Only users with write permissions have access to delete this resource. * @@ -214,9 +206,7 @@ class Storage(client: Client) : Service(client) { .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -230,7 +220,6 @@ class Storage(client: Client) : Service(client) { ) } - /** * Get a file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory. * @@ -249,21 +238,22 @@ class Storage(client: Client) : Service(client) { .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) ) - val apiParams = mutableMapOf( "token" to token, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "*/*", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image. Preview is supported only for image files smaller than 10MB. * @@ -304,7 +294,6 @@ class Storage(client: Client) : Service(client) { .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) ) - val apiParams = mutableMapOf( "width" to width, "height" to height, @@ -318,18 +307,20 @@ class Storage(client: Client) : Service(client) { "background" to background, "output" to output, "token" to token, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "image/*", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - /** * Get a file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header. * @@ -348,19 +339,19 @@ class Storage(client: Client) : Service(client) { .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) ) - val apiParams = mutableMapOf( "token" to token, - "project" to client.config["project"], - "impersonateuserid" to client.config["impersonateuserid"], + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "*/*", ) return client.call( "GET", apiPath, + headers = apiHeaders, params = apiParams, responseType = ByteArray::class.java ) } - - } diff --git a/library/src/main/java/io/appwrite/services/TablesDB.kt b/library/src/main/java/io/appwrite/services/TablesDB.kt index 2bb131d8..0c7554ad 100644 --- a/library/src/main/java/io/appwrite/services/TablesDB.kt +++ b/library/src/main/java/io/appwrite/services/TablesDB.kt @@ -3,17 +3,15 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * - */ + * The TablesDB service allows you to create structured tables of columns, query and filter lists of rows */ class TablesDB(client: Client) : Service(client) { - /** * List transactions across all databases. * @@ -24,9 +22,7 @@ class TablesDB(client: Client) : Service(client) { suspend fun listTransactions( queries: List? = null, ): io.appwrite.models.TransactionList { - val apiPath = ("/tablesdb/transactions" - ) - + val apiPath = "/tablesdb/transactions" val apiParams = mutableMapOf( "queries" to queries, ) @@ -48,7 +44,6 @@ class TablesDB(client: Client) : Service(client) { ) } - /** * Create a new transaction. * @@ -59,9 +54,7 @@ class TablesDB(client: Client) : Service(client) { suspend fun createTransaction( ttl: Long? = null, ): io.appwrite.models.Transaction { - val apiPath = ("/tablesdb/transactions" - ) - + val apiPath = "/tablesdb/transactions" val apiParams = mutableMapOf( "ttl" to ttl, ) @@ -84,7 +77,6 @@ class TablesDB(client: Client) : Service(client) { ) } - /** * Get a transaction by its unique ID. * @@ -97,9 +89,7 @@ class TablesDB(client: Client) : Service(client) { val apiPath = ("/tablesdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -118,7 +108,6 @@ class TablesDB(client: Client) : Service(client) { ) } - /** * Update a transaction, to either commit or roll back its operations. * @@ -136,7 +125,6 @@ class TablesDB(client: Client) : Service(client) { val apiPath = ("/tablesdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) - val apiParams = mutableMapOf( "commit" to commit, "rollback" to rollback, @@ -160,7 +148,6 @@ class TablesDB(client: Client) : Service(client) { ) } - /** * Delete a transaction by its unique ID. * @@ -173,9 +160,7 @@ class TablesDB(client: Client) : Service(client) { val apiPath = ("/tablesdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -189,7 +174,6 @@ class TablesDB(client: Client) : Service(client) { ) } - /** * Create multiple operations in a single transaction. * @@ -200,12 +184,11 @@ class TablesDB(client: Client) : Service(client) { @JvmOverloads suspend fun createOperations( transactionId: String, - operations: List? = null, + operations: List>? = null, ): io.appwrite.models.Transaction { val apiPath = ("/tablesdb/transactions/{transactionId}/operations" .replace("{transactionId}", transactionId) ) - val apiParams = mutableMapOf( "operations" to operations, ) @@ -228,7 +211,6 @@ class TablesDB(client: Client) : Service(client) { ) } - /** * Get a list of all the user's rows in a given table. You can use the query params to filter your results. * @@ -254,7 +236,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) ) - val apiParams = mutableMapOf( "queries" to queries, "transactionId" to transactionId, @@ -325,7 +306,7 @@ class TablesDB(client: Client) : Service(client) { databaseId: String, tableId: String, rowId: String, - data: Any, + data: Map, permissions: List? = null, transactionId: String? = null, nestedType: Class, @@ -334,7 +315,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) ) - val apiParams = mutableMapOf( "rowId" to rowId, "data" to data, @@ -377,7 +357,7 @@ class TablesDB(client: Client) : Service(client) { databaseId: String, tableId: String, rowId: String, - data: Any, + data: Map, permissions: List? = null, transactionId: String? = null, ): io.appwrite.models.Row> = createRow( @@ -414,7 +394,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{tableId}", tableId) .replace("{rowId}", rowId) ) - val apiParams = mutableMapOf( "queries" to queries, "transactionId" to transactionId, @@ -480,7 +459,7 @@ class TablesDB(client: Client) : Service(client) { databaseId: String, tableId: String, rowId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, nestedType: Class, @@ -490,7 +469,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{tableId}", tableId) .replace("{rowId}", rowId) ) - val apiParams = mutableMapOf( "data" to data, "permissions" to permissions, @@ -532,7 +510,7 @@ class TablesDB(client: Client) : Service(client) { databaseId: String, tableId: String, rowId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, ): io.appwrite.models.Row> = upsertRow( @@ -561,7 +539,7 @@ class TablesDB(client: Client) : Service(client) { databaseId: String, tableId: String, rowId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, nestedType: Class, @@ -571,7 +549,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{tableId}", tableId) .replace("{rowId}", rowId) ) - val apiParams = mutableMapOf( "data" to data, "permissions" to permissions, @@ -613,7 +590,7 @@ class TablesDB(client: Client) : Service(client) { databaseId: String, tableId: String, rowId: String, - data: Any? = null, + data: Map? = null, permissions: List? = null, transactionId: String? = null, ): io.appwrite.models.Row> = updateRow( @@ -647,7 +624,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{tableId}", tableId) .replace("{rowId}", rowId) ) - val apiParams = mutableMapOf( "transactionId" to transactionId, ) @@ -664,7 +640,6 @@ class TablesDB(client: Client) : Service(client) { ) } - /** * Decrement a specific column of a row by a given value. * @@ -694,7 +669,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{rowId}", rowId) .replace("{column}", column) ) - val apiParams = mutableMapOf( "value" to value, "min" to min, @@ -781,7 +755,6 @@ class TablesDB(client: Client) : Service(client) { .replace("{rowId}", rowId) .replace("{column}", column) ) - val apiParams = mutableMapOf( "value" to value, "max" to max, @@ -838,5 +811,4 @@ class TablesDB(client: Client) : Service(client) { transactionId, nestedType = classOf(), ) - } diff --git a/library/src/main/java/io/appwrite/services/Teams.kt b/library/src/main/java/io/appwrite/services/Teams.kt index 508b8777..9cf474da 100644 --- a/library/src/main/java/io/appwrite/services/Teams.kt +++ b/library/src/main/java/io/appwrite/services/Teams.kt @@ -3,17 +3,15 @@ package io.appwrite.services import android.net.Uri import io.appwrite.Client import io.appwrite.Service -import io.appwrite.models.* import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.classOf +import io.appwrite.models.* import okhttp3.Cookie import java.io.File /** - * The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources - */ + * The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources */ class Teams(client: Client) : Service(client) { - /** * Get a list of all the teams in which the current user is a member. You can use the parameters to filter your results. * @@ -29,9 +27,7 @@ class Teams(client: Client) : Service(client) { total: Boolean? = null, nestedType: Class, ): io.appwrite.models.TeamList { - val apiPath = ("/teams" - ) - + val apiPath = "/teams" val apiParams = mutableMapOf( "queries" to queries, "search" to search, @@ -91,9 +87,7 @@ class Teams(client: Client) : Service(client) { roles: List? = null, nestedType: Class, ): io.appwrite.models.Team { - val apiPath = ("/teams" - ) - + val apiPath = "/teams" val apiParams = mutableMapOf( "teamId" to teamId, "name" to name, @@ -152,9 +146,7 @@ class Teams(client: Client) : Service(client) { val apiPath = ("/teams/{teamId}" .replace("{teamId}", teamId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -202,7 +194,6 @@ class Teams(client: Client) : Service(client) { val apiPath = ("/teams/{teamId}" .replace("{teamId}", teamId) ) - val apiParams = mutableMapOf( "name" to name, ) @@ -254,12 +245,193 @@ class Teams(client: Client) : Service(client) { val apiPath = ("/teams/{teamId}" .replace("{teamId}", teamId) ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * List app installations on a team. Any team member can read installations. + * + * @param teamId Team ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppInstallationList] + */ + @JvmOverloads + suspend fun listInstallations( + teamId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppInstallationList { + val apiPath = ("/teams/{teamId}/installations" + .replace("{teamId}", teamId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallationList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallationList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallationList::class.java, + converter, + ) + } + + /** + * Install an app on a team. When authenticated as a user, only team members with the owner role can install apps. Requests using an API key or in admin mode can install apps on any team. The installation is granted the scopes the app currently requests. + * + * @param teamId Team ID. + * @param appId Application unique ID. + * @param authorizationDetails Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. The Appwrite Console stores authorized project IDs here. + * @return [io.appwrite.models.AppInstallation] + */ + @JvmOverloads + suspend fun createInstallation( + teamId: String, + appId: String, + authorizationDetails: String? = null, + ): io.appwrite.models.AppInstallation { + val apiPath = ("/teams/{teamId}/installations" + .replace("{teamId}", teamId) + ) + val apiParams = mutableMapOf( + "appId" to appId, + "authorizationDetails" to authorizationDetails, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, + ) + } + + /** + * Get an app installation on a team by its unique ID. Any team member can read installations. + * + * @param teamId Team ID. + * @param installationId Installation unique ID. + * @return [io.appwrite.models.AppInstallation] + */ + suspend fun getInstallation( + teamId: String, + installationId: String, + ): io.appwrite.models.AppInstallation { + val apiPath = ("/teams/{teamId}/installations/{installationId}" + .replace("{teamId}", teamId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, + ) + } + /** + * Update an app installation on a team. Only team members with the owner role can update installations. The installation's granted scopes are refreshed to the scopes the app currently requests; previously issued installation access tokens are revoked. + * + * @param teamId Team ID. + * @param installationId Installation unique ID. + * @param authorizationDetails Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. Omit to keep the current value. + * @return [io.appwrite.models.AppInstallation] + */ + @JvmOverloads + suspend fun updateInstallation( + teamId: String, + installationId: String, + authorizationDetails: String? = null, + ): io.appwrite.models.AppInstallation { + val apiPath = ("/teams/{teamId}/installations/{installationId}" + .replace("{teamId}", teamId) + .replace("{installationId}", installationId) + ) val apiParams = mutableMapOf( + "authorizationDetails" to authorizationDetails, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, ) + } + + /** + * Uninstall an app from a team by its installation ID. Only team members with the owner role can remove installations. Previously issued installation access tokens are revoked. + * + * @param teamId Team ID. + * @param installationId Installation unique ID. + * @return [Any] + */ + suspend fun deleteInstallation( + teamId: String, + installationId: String, + ): Any { + val apiPath = ("/teams/{teamId}/installations/{installationId}" + .replace("{teamId}", teamId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -270,7 +442,6 @@ class Teams(client: Client) : Service(client) { ) } - /** * Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console. * @@ -290,7 +461,6 @@ class Teams(client: Client) : Service(client) { val apiPath = ("/teams/{teamId}/memberships" .replace("{teamId}", teamId) ) - val apiParams = mutableMapOf( "queries" to queries, "search" to search, @@ -314,16 +484,15 @@ class Teams(client: Client) : Service(client) { ) } - /** * Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team. - * + * * You only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters. - * - * Use the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https://appwrite.io/docs/references/cloud/client-web/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. - * + * + * Use the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https://appwrite.io/docs/references/cloud/client-web/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. + * * Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console. - * + * * * @param teamId Team ID. * @param roles Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long. @@ -347,7 +516,6 @@ class Teams(client: Client) : Service(client) { val apiPath = ("/teams/{teamId}/memberships" .replace("{teamId}", teamId) ) - val apiParams = mutableMapOf( "email" to email, "userId" to userId, @@ -375,7 +543,6 @@ class Teams(client: Client) : Service(client) { ) } - /** * Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console. * @@ -391,9 +558,7 @@ class Teams(client: Client) : Service(client) { .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -412,10 +577,9 @@ class Teams(client: Client) : Service(client) { ) } - /** * Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). - * + * * * @param teamId Team ID. * @param membershipId Membership ID. @@ -431,7 +595,6 @@ class Teams(client: Client) : Service(client) { .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) ) - val apiParams = mutableMapOf( "roles" to roles, ) @@ -454,7 +617,6 @@ class Teams(client: Client) : Service(client) { ) } - /** * This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if it is not accepted. * @@ -470,9 +632,7 @@ class Teams(client: Client) : Service(client) { .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -486,12 +646,11 @@ class Teams(client: Client) : Service(client) { ) } - /** * Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user. - * + * * If the request is successful, a session for the user is automatically created. - * + * * * @param teamId Team ID. * @param membershipId Membership ID. @@ -509,7 +668,6 @@ class Teams(client: Client) : Service(client) { .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) ) - val apiParams = mutableMapOf( "userId" to userId, "secret" to secret, @@ -533,7 +691,6 @@ class Teams(client: Client) : Service(client) { ) } - /** * Get the team's shared preferences by its unique ID. If a preference doesn't need to be shared by all team members, prefer storing them in [user preferences](https://appwrite.io/docs/references/cloud/client-web/account#getPrefs). * @@ -547,9 +704,7 @@ class Teams(client: Client) : Service(client) { val apiPath = ("/teams/{teamId}/prefs" .replace("{teamId}", teamId) ) - - val apiParams = mutableMapOf( - ) + val apiParams = mutableMapOf() val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -591,13 +746,12 @@ class Teams(client: Client) : Service(client) { */ suspend fun updatePrefs( teamId: String, - prefs: Any, + prefs: Map, nestedType: Class, ): io.appwrite.models.Preferences { val apiPath = ("/teams/{teamId}/prefs" .replace("{teamId}", teamId) ) - val apiParams = mutableMapOf( "prefs" to prefs, ) @@ -630,11 +784,10 @@ class Teams(client: Client) : Service(client) { @Throws(AppwriteException::class) suspend fun updatePrefs( teamId: String, - prefs: Any, + prefs: Map, ): io.appwrite.models.Preferences> = updatePrefs( teamId, prefs, nestedType = classOf(), ) - } diff --git a/library/src/main/java/io/appwrite/views/CallbackActivity.kt b/library/src/main/java/io/appwrite/views/CallbackActivity.kt index 0867849e..d816e256 100644 --- a/library/src/main/java/io/appwrite/views/CallbackActivity.kt +++ b/library/src/main/java/io/appwrite/views/CallbackActivity.kt @@ -4,7 +4,7 @@ import android.app.Activity import android.os.Bundle import io.appwrite.WebAuthComponent -class CallbackActivity: Activity() { +class CallbackActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -17,4 +17,4 @@ class CallbackActivity: Activity() { } finish() } -} \ No newline at end of file +} diff --git a/library/src/test/java/io/appwrite/JsonRequestBodyTest.kt b/library/src/test/java/io/appwrite/JsonRequestBodyTest.kt new file mode 100644 index 00000000..9ad2ce8e --- /dev/null +++ b/library/src/test/java/io/appwrite/JsonRequestBodyTest.kt @@ -0,0 +1,33 @@ +package io.appwrite + +import io.appwrite.extensions.toJson +import io.appwrite.extensions.toJsonRequestBody +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class JsonRequestBodyTest { + @Test + fun jsonRequestBodyPreservesNestedNullsAndOmitsOptionalParams() { + val params = mapOf( + "data" to mapOf( + "backgroundType" to "SOLID_COLOR", + "solidColorIndex" to null, + "title" to null, + ), + "permissions" to null, + ) + + val body = params.toJsonRequestBody() + assertTrue(body.contains("\"solidColorIndex\":null")) + assertTrue(body.contains("\"title\":null")) + assertTrue(body.contains("\"backgroundType\":\"SOLID_COLOR\"")) + assertFalse(body.contains("permissions")) + + // Default Gson still omits nulls so Query/Operator JSON stays compact. + val withoutNulls = params.filterValues { it != null }.toJson() + assertFalse(withoutNulls.contains("\"solidColorIndex\":null")) + assertFalse(withoutNulls.contains("\"title\":null")) + assertTrue(withoutNulls.contains("\"backgroundType\":\"SOLID_COLOR\"")) + } +} From ea9b64ab1ead391b975a16a5d0fcacb02b82477b Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 7 Sep 2026 15:58:20 +0100 Subject: [PATCH 06/12] chore: update Android SDK to 27.2.0 --- CHANGELOG.md | 14 + README.md | 8 +- build.gradle.kts | 4 +- docs/examples/java/avatars/get-initials.md | 2 +- docs/examples/java/avatars/get-photo.md | 3 + .../databases/decrement-document-attribute.md | 4 +- .../databases/increment-document-attribute.md | 6 +- .../java/documentsdb/create-document.md | 39 + .../java/documentsdb/create-documents.md | 29 + .../java/documentsdb/create-operations.md | 35 + .../java/documentsdb/create-transaction.md | 26 + .../decrement-document-attribute.md | 32 + .../java/documentsdb/delete-document.md | 29 + .../java/documentsdb/delete-transaction.md | 26 + .../examples/java/documentsdb/get-document.md | 30 + .../java/documentsdb/get-transaction.md | 26 + .../increment-document-attribute.md | 32 + .../java/documentsdb/list-documents.md | 31 + .../java/documentsdb/list-transactions.md | 26 + .../java/documentsdb/update-document.md | 33 + .../java/documentsdb/update-transaction.md | 28 + .../java/documentsdb/upsert-document.md | 33 + docs/examples/java/storage/create-file.md | 2 +- .../examples/java/storage/get-file-preview.md | 4 +- .../java/tablesdb/decrement-row-column.md | 4 +- .../java/tablesdb/increment-row-column.md | 6 +- .../java/vectorsdb/create-document.md | 38 + .../java/vectorsdb/create-operations.md | 35 + docs/examples/java/vectorsdb/create-query.md | 31 + .../java/vectorsdb/create-transaction.md | 26 + .../java/vectorsdb/delete-document.md | 29 + .../java/vectorsdb/delete-transaction.md | 26 + docs/examples/java/vectorsdb/get-document.md | 30 + .../java/vectorsdb/get-transaction.md | 26 + .../examples/java/vectorsdb/list-documents.md | 31 + .../java/vectorsdb/list-transactions.md | 26 + .../java/vectorsdb/update-document.md | 33 + .../java/vectorsdb/update-transaction.md | 28 + .../java/vectorsdb/upsert-document.md | 33 + docs/examples/kotlin/avatars/get-initials.md | 2 +- docs/examples/kotlin/avatars/get-photo.md | 3 + .../databases/decrement-document-attribute.md | 4 +- .../databases/increment-document-attribute.md | 6 +- .../kotlin/documentsdb/create-document.md | 28 + .../kotlin/documentsdb/create-documents.md | 18 + .../kotlin/documentsdb/create-operations.md | 24 + .../kotlin/documentsdb/create-transaction.md | 15 + .../decrement-document-attribute.md | 21 + .../kotlin/documentsdb/delete-document.md | 18 + .../kotlin/documentsdb/delete-transaction.md | 15 + .../kotlin/documentsdb/get-document.md | 19 + .../kotlin/documentsdb/get-transaction.md | 15 + .../increment-document-attribute.md | 21 + .../kotlin/documentsdb/list-documents.md | 20 + .../kotlin/documentsdb/list-transactions.md | 15 + .../kotlin/documentsdb/update-document.md | 22 + .../kotlin/documentsdb/update-transaction.md | 17 + .../kotlin/documentsdb/upsert-document.md | 22 + docs/examples/kotlin/storage/create-file.md | 2 +- .../kotlin/storage/get-file-preview.md | 4 +- .../kotlin/tablesdb/decrement-row-column.md | 4 +- .../kotlin/tablesdb/increment-row-column.md | 6 +- .../kotlin/vectorsdb/create-document.md | 27 + .../kotlin/vectorsdb/create-operations.md | 24 + .../examples/kotlin/vectorsdb/create-query.md | 20 + .../kotlin/vectorsdb/create-transaction.md | 15 + .../kotlin/vectorsdb/delete-document.md | 18 + .../kotlin/vectorsdb/delete-transaction.md | 15 + .../examples/kotlin/vectorsdb/get-document.md | 19 + .../kotlin/vectorsdb/get-transaction.md | 15 + .../kotlin/vectorsdb/list-documents.md | 20 + .../kotlin/vectorsdb/list-transactions.md | 15 + .../kotlin/vectorsdb/update-document.md | 22 + .../kotlin/vectorsdb/update-transaction.md | 17 + .../kotlin/vectorsdb/upsert-document.md | 22 + example/build.gradle.kts | 6 +- library/src/main/java/io/appwrite/Client.kt | 6 +- .../java/io/appwrite/enums/OAuthProvider.kt | 4 + .../main/java/io/appwrite/services/Account.kt | 4 +- .../main/java/io/appwrite/services/Avatars.kt | 13 +- .../java/io/appwrite/services/DocumentsDB.kt | 882 ++++++++++++++++++ .../java/io/appwrite/services/Realtime.kt | 53 +- .../java/io/appwrite/services/VectorsDB.kt | 724 ++++++++++++++ .../io/appwrite/ClientJavaInteropTest.java | 25 + 84 files changed, 3142 insertions(+), 49 deletions(-) create mode 100644 docs/examples/java/documentsdb/create-document.md create mode 100644 docs/examples/java/documentsdb/create-documents.md create mode 100644 docs/examples/java/documentsdb/create-operations.md create mode 100644 docs/examples/java/documentsdb/create-transaction.md create mode 100644 docs/examples/java/documentsdb/decrement-document-attribute.md create mode 100644 docs/examples/java/documentsdb/delete-document.md create mode 100644 docs/examples/java/documentsdb/delete-transaction.md create mode 100644 docs/examples/java/documentsdb/get-document.md create mode 100644 docs/examples/java/documentsdb/get-transaction.md create mode 100644 docs/examples/java/documentsdb/increment-document-attribute.md create mode 100644 docs/examples/java/documentsdb/list-documents.md create mode 100644 docs/examples/java/documentsdb/list-transactions.md create mode 100644 docs/examples/java/documentsdb/update-document.md create mode 100644 docs/examples/java/documentsdb/update-transaction.md create mode 100644 docs/examples/java/documentsdb/upsert-document.md create mode 100644 docs/examples/java/vectorsdb/create-document.md create mode 100644 docs/examples/java/vectorsdb/create-operations.md create mode 100644 docs/examples/java/vectorsdb/create-query.md create mode 100644 docs/examples/java/vectorsdb/create-transaction.md create mode 100644 docs/examples/java/vectorsdb/delete-document.md create mode 100644 docs/examples/java/vectorsdb/delete-transaction.md create mode 100644 docs/examples/java/vectorsdb/get-document.md create mode 100644 docs/examples/java/vectorsdb/get-transaction.md create mode 100644 docs/examples/java/vectorsdb/list-documents.md create mode 100644 docs/examples/java/vectorsdb/list-transactions.md create mode 100644 docs/examples/java/vectorsdb/update-document.md create mode 100644 docs/examples/java/vectorsdb/update-transaction.md create mode 100644 docs/examples/java/vectorsdb/upsert-document.md create mode 100644 docs/examples/kotlin/documentsdb/create-document.md create mode 100644 docs/examples/kotlin/documentsdb/create-documents.md create mode 100644 docs/examples/kotlin/documentsdb/create-operations.md create mode 100644 docs/examples/kotlin/documentsdb/create-transaction.md create mode 100644 docs/examples/kotlin/documentsdb/decrement-document-attribute.md create mode 100644 docs/examples/kotlin/documentsdb/delete-document.md create mode 100644 docs/examples/kotlin/documentsdb/delete-transaction.md create mode 100644 docs/examples/kotlin/documentsdb/get-document.md create mode 100644 docs/examples/kotlin/documentsdb/get-transaction.md create mode 100644 docs/examples/kotlin/documentsdb/increment-document-attribute.md create mode 100644 docs/examples/kotlin/documentsdb/list-documents.md create mode 100644 docs/examples/kotlin/documentsdb/list-transactions.md create mode 100644 docs/examples/kotlin/documentsdb/update-document.md create mode 100644 docs/examples/kotlin/documentsdb/update-transaction.md create mode 100644 docs/examples/kotlin/documentsdb/upsert-document.md create mode 100644 docs/examples/kotlin/vectorsdb/create-document.md create mode 100644 docs/examples/kotlin/vectorsdb/create-operations.md create mode 100644 docs/examples/kotlin/vectorsdb/create-query.md create mode 100644 docs/examples/kotlin/vectorsdb/create-transaction.md create mode 100644 docs/examples/kotlin/vectorsdb/delete-document.md create mode 100644 docs/examples/kotlin/vectorsdb/delete-transaction.md create mode 100644 docs/examples/kotlin/vectorsdb/get-document.md create mode 100644 docs/examples/kotlin/vectorsdb/get-transaction.md create mode 100644 docs/examples/kotlin/vectorsdb/list-documents.md create mode 100644 docs/examples/kotlin/vectorsdb/list-transactions.md create mode 100644 docs/examples/kotlin/vectorsdb/update-document.md create mode 100644 docs/examples/kotlin/vectorsdb/update-transaction.md create mode 100644 docs/examples/kotlin/vectorsdb/upsert-document.md create mode 100644 library/src/main/java/io/appwrite/services/DocumentsDB.kt create mode 100644 library/src/main/java/io/appwrite/services/VectorsDB.kt create mode 100644 library/src/test/java/io/appwrite/ClientJavaInteropTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 700ff96c..75f5921b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## 27.2.0 + +* Added: `DocumentsDB` and `VectorsDB` services for document and vector database access +* Added: `userId`, `emailHash` and `name` parameters to `Avatars.getPhoto()` +* Added: `CLOUDFLARE` and `RESEND` values to `OAuthProvider` enum +* Fixed: `Client.setEndpoint` and `setEndpointRealtime` are fluent when called from Java +* Updated: `x-appwrite-response-format` header to `2.0.0` + +## 27.1.0 + +* Added: `Realtime.onError`, `onOpen`, and `onClose` lifecycle callbacks +* Fixed: message-handler exceptions are delivered via `onError` instead of crashing the coroutine +* Fixed: lifecycle callbacks are isolated per connection and cleared on disconnect + ## 27.0.0 * Breaking: object parameters and model fields are now typed `Map` instead of `Any` diff --git a/README.md b/README.md index 62e9d751..ac566f68 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ ![Maven Central](https://img.shields.io/maven-central/v/io.appwrite/sdk-for-android.svg?color=green&style=flat-square) ![License](https://img.shields.io/github/license/appwrite/sdk-for-android.svg?style=flat-square) -![Version](https://img.shields.io/badge/api%20version-1.9.6-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-2.0.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) -**This SDK is compatible with Appwrite server version 1.9.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-android/releases).** +**This SDK targets Appwrite server version 1.9.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-android/releases) when APIs differ. Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Android SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs) @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:27.0.0") +implementation("io.appwrite:sdk-for-android:27.2.0") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 27.0.0 + 27.2.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 252363de..2239ad56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,8 +2,8 @@ import java.util.Properties plugins { id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.android.library") version "9.3.2" apply false - id("com.android.application") version "9.3.2" apply false + id("com.android.library") version "9.4.0" apply false + id("com.android.application") version "9.4.0" apply false } version = System.getenv("SDK_VERSION") ?: "0.0.0" diff --git a/docs/examples/java/avatars/get-initials.md b/docs/examples/java/avatars/get-initials.md index 1b781b67..08f5bea9 100644 --- a/docs/examples/java/avatars/get-initials.md +++ b/docs/examples/java/avatars/get-initials.md @@ -15,7 +15,7 @@ avatars.getInitials( "", // name (optional) 0, // width (optional) 0, // height (optional) - "", // background (optional) + "FFFFFF", // background (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-photo.md b/docs/examples/java/avatars/get-photo.md index b1d14dca..2204db78 100644 --- a/docs/examples/java/avatars/get-photo.md +++ b/docs/examples/java/avatars/get-photo.md @@ -17,6 +17,9 @@ avatars.getPhoto( 0, // quality (optional) "png", // output (optional) "g", // rating (optional) + "current()", // userId (optional) + "", // emailHash (optional) + "", // name (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/databases/decrement-document-attribute.md b/docs/examples/java/databases/decrement-document-attribute.md index f069abbf..1a2f23e2 100644 --- a/docs/examples/java/databases/decrement-document-attribute.md +++ b/docs/examples/java/databases/decrement-document-attribute.md @@ -15,8 +15,8 @@ databases.decrementDocumentAttribute( "", // databaseId "", // collectionId "", // documentId - "", // attribute - 0, // value (optional) + "", // attribute + 1, // value (optional) 0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/java/databases/increment-document-attribute.md b/docs/examples/java/databases/increment-document-attribute.md index c41a9071..fe363592 100644 --- a/docs/examples/java/databases/increment-document-attribute.md +++ b/docs/examples/java/databases/increment-document-attribute.md @@ -15,9 +15,9 @@ databases.incrementDocumentAttribute( "", // databaseId "", // collectionId "", // documentId - "", // attribute - 0, // value (optional) - 0, // max (optional) + "", // attribute + 1, // value (optional) + 100, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/documentsdb/create-document.md b/docs/examples/java/documentsdb/create-document.md new file mode 100644 index 00000000..77f034d8 --- /dev/null +++ b/docs/examples/java/documentsdb/create-document.md @@ -0,0 +1,39 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createDocument( + "", // databaseId + "", // collectionId + "", // documentId + Map.of( + "username", "walter.obrien", + "email", "walter.obrien@example.com", + "fullName", "Walter O'Brien", + "age", 30, + "isAdmin", false + ), // data + List.of(Permission.read(Role.any())), // permissions (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/create-documents.md b/docs/examples/java/documentsdb/create-documents.md new file mode 100644 index 00000000..c52433a2 --- /dev/null +++ b/docs/examples/java/documentsdb/create-documents.md @@ -0,0 +1,29 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createDocuments( + "", // databaseId + "", // collectionId + List.of(), // documents + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/create-operations.md b/docs/examples/java/documentsdb/create-operations.md new file mode 100644 index 00000000..961a5501 --- /dev/null +++ b/docs/examples/java/documentsdb/create-operations.md @@ -0,0 +1,35 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createOperations( + "", // transactionId + List.of(Map.of( + "action", "create", + "databaseId", "", + "collectionId", "", + "documentId", "", + "data", Map.of( + "name", "Walter O'Brien" + ) + )), // operations (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/create-transaction.md b/docs/examples/java/documentsdb/create-transaction.md new file mode 100644 index 00000000..da91c5c8 --- /dev/null +++ b/docs/examples/java/documentsdb/create-transaction.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.createTransaction( + 60, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/decrement-document-attribute.md b/docs/examples/java/documentsdb/decrement-document-attribute.md new file mode 100644 index 00000000..f1853ac6 --- /dev/null +++ b/docs/examples/java/documentsdb/decrement-document-attribute.md @@ -0,0 +1,32 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.decrementDocumentAttribute( + "", // databaseId + "", // collectionId + "", // documentId + "", // attribute + 1, // value (optional) + 0, // min (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/delete-document.md b/docs/examples/java/documentsdb/delete-document.md new file mode 100644 index 00000000..247a63aa --- /dev/null +++ b/docs/examples/java/documentsdb/delete-document.md @@ -0,0 +1,29 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.deleteDocument( + "", // databaseId + "", // collectionId + "", // documentId + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/delete-transaction.md b/docs/examples/java/documentsdb/delete-transaction.md new file mode 100644 index 00000000..0b0a7616 --- /dev/null +++ b/docs/examples/java/documentsdb/delete-transaction.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.deleteTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/get-document.md b/docs/examples/java/documentsdb/get-document.md new file mode 100644 index 00000000..7bd66e1e --- /dev/null +++ b/docs/examples/java/documentsdb/get-document.md @@ -0,0 +1,30 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.getDocument( + "", // databaseId + "", // collectionId + "", // documentId + List.of(), // queries (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/get-transaction.md b/docs/examples/java/documentsdb/get-transaction.md new file mode 100644 index 00000000..12c42243 --- /dev/null +++ b/docs/examples/java/documentsdb/get-transaction.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.getTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/increment-document-attribute.md b/docs/examples/java/documentsdb/increment-document-attribute.md new file mode 100644 index 00000000..fab214e9 --- /dev/null +++ b/docs/examples/java/documentsdb/increment-document-attribute.md @@ -0,0 +1,32 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.incrementDocumentAttribute( + "", // databaseId + "", // collectionId + "", // documentId + "", // attribute + 1, // value (optional) + 100, // max (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/list-documents.md b/docs/examples/java/documentsdb/list-documents.md new file mode 100644 index 00000000..0abb6734 --- /dev/null +++ b/docs/examples/java/documentsdb/list-documents.md @@ -0,0 +1,31 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.listDocuments( + "", // databaseId + "", // collectionId + List.of(), // queries (optional) + "", // transactionId (optional) + false, // total (optional) + 0, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/list-transactions.md b/docs/examples/java/documentsdb/list-transactions.md new file mode 100644 index 00000000..9b8fcd7b --- /dev/null +++ b/docs/examples/java/documentsdb/list-transactions.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.listTransactions( + List.of(), // queries (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/update-document.md b/docs/examples/java/documentsdb/update-document.md new file mode 100644 index 00000000..da8475fa --- /dev/null +++ b/docs/examples/java/documentsdb/update-document.md @@ -0,0 +1,33 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.updateDocument( + "", // databaseId + "", // collectionId + "", // documentId + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/update-transaction.md b/docs/examples/java/documentsdb/update-transaction.md new file mode 100644 index 00000000..9d95dfcd --- /dev/null +++ b/docs/examples/java/documentsdb/update-transaction.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.updateTransaction( + "", // transactionId + false, // commit (optional) + false, // rollback (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/documentsdb/upsert-document.md b/docs/examples/java/documentsdb/upsert-document.md new file mode 100644 index 00000000..82e90a40 --- /dev/null +++ b/docs/examples/java/documentsdb/upsert-document.md @@ -0,0 +1,33 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.services.DocumentsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +DocumentsDB documentsDB = new DocumentsDB(client); + +documentsDB.upsertDocument( + "", // databaseId + "", // collectionId + "", // documentId + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/storage/create-file.md b/docs/examples/java/storage/create-file.md index bcc8296b..e441255e 100644 --- a/docs/examples/java/storage/create-file.md +++ b/docs/examples/java/storage/create-file.md @@ -19,7 +19,7 @@ storage.createFile( "", // fileId InputFile.fromPath("file.png"), // file List.of(Permission.read(Role.any())), // permissions (optional) - "", // folder (optional) + "photos/2026", // folder (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/storage/get-file-preview.md b/docs/examples/java/storage/get-file-preview.md index f7282076..023a7604 100644 --- a/docs/examples/java/storage/get-file-preview.md +++ b/docs/examples/java/storage/get-file-preview.md @@ -21,11 +21,11 @@ storage.getFilePreview( ImageGravity.CENTER, // gravity (optional) -1, // quality (optional) 0, // borderWidth (optional) - "", // borderColor (optional) + "FFFFFF", // borderColor (optional) 0, // borderRadius (optional) 0, // opacity (optional) -360, // rotation (optional) - "", // background (optional) + "FFFFFF", // background (optional) ImageFormat.JPG, // output (optional) "", // token (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/java/tablesdb/decrement-row-column.md b/docs/examples/java/tablesdb/decrement-row-column.md index 5f9b7ce1..c998b442 100644 --- a/docs/examples/java/tablesdb/decrement-row-column.md +++ b/docs/examples/java/tablesdb/decrement-row-column.md @@ -15,8 +15,8 @@ tablesDB.decrementRowColumn( "", // databaseId "", // tableId "", // rowId - "", // column - 0, // value (optional) + "", // column + 1, // value (optional) 0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/java/tablesdb/increment-row-column.md b/docs/examples/java/tablesdb/increment-row-column.md index ae97cdb8..5fb7189e 100644 --- a/docs/examples/java/tablesdb/increment-row-column.md +++ b/docs/examples/java/tablesdb/increment-row-column.md @@ -15,9 +15,9 @@ tablesDB.incrementRowColumn( "", // databaseId "", // tableId "", // rowId - "", // column - 0, // value (optional) - 0, // max (optional) + "", // column + 1, // value (optional) + 100, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/vectorsdb/create-document.md b/docs/examples/java/vectorsdb/create-document.md new file mode 100644 index 00000000..8a3d7f9e --- /dev/null +++ b/docs/examples/java/vectorsdb/create-document.md @@ -0,0 +1,38 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createDocument( + "", // databaseId + "", // collectionId + "", // documentId + Map.of( + "embeddings", List.of(0.12, -0.55, 0.88, 1.02), + "metadata", Map.of( + "key", "value" + ) + ), // data + List.of(Permission.read(Role.any())), // permissions (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/create-operations.md b/docs/examples/java/vectorsdb/create-operations.md new file mode 100644 index 00000000..7e503634 --- /dev/null +++ b/docs/examples/java/vectorsdb/create-operations.md @@ -0,0 +1,35 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createOperations( + "", // transactionId + List.of(Map.of( + "action", "create", + "databaseId", "", + "collectionId", "", + "documentId", "", + "data", Map.of( + "name", "Walter O'Brien" + ) + )), // operations (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/create-query.md b/docs/examples/java/vectorsdb/create-query.md new file mode 100644 index 00000000..3f1cfd5c --- /dev/null +++ b/docs/examples/java/vectorsdb/create-query.md @@ -0,0 +1,31 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createQuery( + "", // databaseId + "", // collectionId + List.of(), // queries (optional) + "", // transactionId (optional) + false, // total (optional) + 0, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/create-transaction.md b/docs/examples/java/vectorsdb/create-transaction.md new file mode 100644 index 00000000..4784fc47 --- /dev/null +++ b/docs/examples/java/vectorsdb/create-transaction.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.createTransaction( + 60, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/delete-document.md b/docs/examples/java/vectorsdb/delete-document.md new file mode 100644 index 00000000..d8067301 --- /dev/null +++ b/docs/examples/java/vectorsdb/delete-document.md @@ -0,0 +1,29 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.deleteDocument( + "", // databaseId + "", // collectionId + "", // documentId + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/delete-transaction.md b/docs/examples/java/vectorsdb/delete-transaction.md new file mode 100644 index 00000000..04b9c4da --- /dev/null +++ b/docs/examples/java/vectorsdb/delete-transaction.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.deleteTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/get-document.md b/docs/examples/java/vectorsdb/get-document.md new file mode 100644 index 00000000..15ddf82d --- /dev/null +++ b/docs/examples/java/vectorsdb/get-document.md @@ -0,0 +1,30 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.getDocument( + "", // databaseId + "", // collectionId + "", // documentId + List.of(), // queries (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/get-transaction.md b/docs/examples/java/vectorsdb/get-transaction.md new file mode 100644 index 00000000..987ca6e7 --- /dev/null +++ b/docs/examples/java/vectorsdb/get-transaction.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.getTransaction( + "", // transactionId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/list-documents.md b/docs/examples/java/vectorsdb/list-documents.md new file mode 100644 index 00000000..12ef1f0c --- /dev/null +++ b/docs/examples/java/vectorsdb/list-documents.md @@ -0,0 +1,31 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listDocuments( + "", // databaseId + "", // collectionId + List.of(), // queries (optional) + "", // transactionId (optional) + false, // total (optional) + 0, // ttl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/list-transactions.md b/docs/examples/java/vectorsdb/list-transactions.md new file mode 100644 index 00000000..18ef0e55 --- /dev/null +++ b/docs/examples/java/vectorsdb/list-transactions.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.listTransactions( + List.of(), // queries (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/update-document.md b/docs/examples/java/vectorsdb/update-document.md new file mode 100644 index 00000000..4acb5cce --- /dev/null +++ b/docs/examples/java/vectorsdb/update-document.md @@ -0,0 +1,33 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateDocument( + "", // databaseId + "", // collectionId + "", // documentId + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/update-transaction.md b/docs/examples/java/vectorsdb/update-transaction.md new file mode 100644 index 00000000..a21aad1a --- /dev/null +++ b/docs/examples/java/vectorsdb/update-transaction.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.updateTransaction( + "", // transactionId + false, // commit (optional) + false, // rollback (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/vectorsdb/upsert-document.md b/docs/examples/java/vectorsdb/upsert-document.md new file mode 100644 index 00000000..58ac9a9a --- /dev/null +++ b/docs/examples/java/vectorsdb/upsert-document.md @@ -0,0 +1,33 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.Permission; +import io.appwrite.Role; +import io.appwrite.services.VectorsDB; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +VectorsDB vectorsDB = new VectorsDB(client); + +vectorsDB.upsertDocument( + "", // databaseId + "", // collectionId + "", // documentId + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) + "", // transactionId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/kotlin/avatars/get-initials.md b/docs/examples/kotlin/avatars/get-initials.md index 1cb76a18..31b1f474 100644 --- a/docs/examples/kotlin/avatars/get-initials.md +++ b/docs/examples/kotlin/avatars/get-initials.md @@ -13,6 +13,6 @@ val result = avatars.getInitials( name = "", // (optional) width = 0, // (optional) height = 0, // (optional) - background = "", // (optional) + background = "FFFFFF", // (optional) ) ``` diff --git a/docs/examples/kotlin/avatars/get-photo.md b/docs/examples/kotlin/avatars/get-photo.md index a05805fb..d1127ba6 100644 --- a/docs/examples/kotlin/avatars/get-photo.md +++ b/docs/examples/kotlin/avatars/get-photo.md @@ -15,5 +15,8 @@ val result = avatars.getPhoto( quality = 0, // (optional) output = "png", // (optional) rating = "g", // (optional) + userId = "current()", // (optional) + emailHash = "", // (optional) + name = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/databases/decrement-document-attribute.md b/docs/examples/kotlin/databases/decrement-document-attribute.md index 9ca780fd..c32a6002 100644 --- a/docs/examples/kotlin/databases/decrement-document-attribute.md +++ b/docs/examples/kotlin/databases/decrement-document-attribute.md @@ -13,8 +13,8 @@ val result = databases.decrementDocumentAttribute( databaseId = "", collectionId = "", documentId = "", - attribute = "", - value = 0, // (optional) + attribute = "", + value = 1, // (optional) min = 0, // (optional) transactionId = "", // (optional) ) diff --git a/docs/examples/kotlin/databases/increment-document-attribute.md b/docs/examples/kotlin/databases/increment-document-attribute.md index 20afc996..daaa6d6a 100644 --- a/docs/examples/kotlin/databases/increment-document-attribute.md +++ b/docs/examples/kotlin/databases/increment-document-attribute.md @@ -13,9 +13,9 @@ val result = databases.incrementDocumentAttribute( databaseId = "", collectionId = "", documentId = "", - attribute = "", - value = 0, // (optional) - max = 0, // (optional) + attribute = "", + value = 1, // (optional) + max = 100, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/create-document.md b/docs/examples/kotlin/documentsdb/create-document.md new file mode 100644 index 00000000..e2e493f8 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/create-document.md @@ -0,0 +1,28 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB +import io.appwrite.Permission +import io.appwrite.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( + "username" to "walter.obrien", + "email" to "walter.obrien@example.com", + "fullName" to "Walter O'Brien", + "age" to 30, + "isAdmin" to false + ), + permissions = listOf(Permission.read(Role.any())), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/create-documents.md b/docs/examples/kotlin/documentsdb/create-documents.md new file mode 100644 index 00000000..2923c100 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/create-documents.md @@ -0,0 +1,18 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.createDocuments( + databaseId = "", + collectionId = "", + documents = listOf(), + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/create-operations.md b/docs/examples/kotlin/documentsdb/create-operations.md new file mode 100644 index 00000000..c6258095 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/create-operations.md @@ -0,0 +1,24 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.createOperations( + transactionId = "", + operations = listOf(mapOf( + "action" to "create", + "databaseId" to "", + "collectionId" to "", + "documentId" to "", + "data" to mapOf( + "name" to "Walter O'Brien" + ) + )), // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/create-transaction.md b/docs/examples/kotlin/documentsdb/create-transaction.md new file mode 100644 index 00000000..ad1f0b21 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/create-transaction.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.createTransaction( + ttl = 60, // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/decrement-document-attribute.md b/docs/examples/kotlin/documentsdb/decrement-document-attribute.md new file mode 100644 index 00000000..fdb11fe0 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/decrement-document-attribute.md @@ -0,0 +1,21 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.decrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "", + value = 1, // (optional) + min = 0, // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/delete-document.md b/docs/examples/kotlin/documentsdb/delete-document.md new file mode 100644 index 00000000..648a83ca --- /dev/null +++ b/docs/examples/kotlin/documentsdb/delete-document.md @@ -0,0 +1,18 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.deleteDocument( + databaseId = "", + collectionId = "", + documentId = "", + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/delete-transaction.md b/docs/examples/kotlin/documentsdb/delete-transaction.md new file mode 100644 index 00000000..d991466f --- /dev/null +++ b/docs/examples/kotlin/documentsdb/delete-transaction.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.deleteTransaction( + transactionId = "", +) +``` diff --git a/docs/examples/kotlin/documentsdb/get-document.md b/docs/examples/kotlin/documentsdb/get-document.md new file mode 100644 index 00000000..1988e002 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/get-document.md @@ -0,0 +1,19 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.getDocument( + databaseId = "", + collectionId = "", + documentId = "", + queries = listOf(), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/get-transaction.md b/docs/examples/kotlin/documentsdb/get-transaction.md new file mode 100644 index 00000000..695412e7 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/get-transaction.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.getTransaction( + transactionId = "", +) +``` diff --git a/docs/examples/kotlin/documentsdb/increment-document-attribute.md b/docs/examples/kotlin/documentsdb/increment-document-attribute.md new file mode 100644 index 00000000..8286ad44 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/increment-document-attribute.md @@ -0,0 +1,21 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.incrementDocumentAttribute( + databaseId = "", + collectionId = "", + documentId = "", + attribute = "", + value = 1, // (optional) + max = 100, // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/list-documents.md b/docs/examples/kotlin/documentsdb/list-documents.md new file mode 100644 index 00000000..a8c1b66b --- /dev/null +++ b/docs/examples/kotlin/documentsdb/list-documents.md @@ -0,0 +1,20 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf(), // (optional) + transactionId = "", // (optional) + total = false, // (optional) + ttl = 0, // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/list-transactions.md b/docs/examples/kotlin/documentsdb/list-transactions.md new file mode 100644 index 00000000..46571468 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/list-transactions.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.listTransactions( + queries = listOf(), // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/update-document.md b/docs/examples/kotlin/documentsdb/update-document.md new file mode 100644 index 00000000..69668863 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/update-document.md @@ -0,0 +1,22 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB +import io.appwrite.Permission +import io.appwrite.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( "a" to "b" ), // (optional) + permissions = listOf(Permission.read(Role.any())), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/update-transaction.md b/docs/examples/kotlin/documentsdb/update-transaction.md new file mode 100644 index 00000000..78fb3e7b --- /dev/null +++ b/docs/examples/kotlin/documentsdb/update-transaction.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.updateTransaction( + transactionId = "", + commit = false, // (optional) + rollback = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/documentsdb/upsert-document.md b/docs/examples/kotlin/documentsdb/upsert-document.md new file mode 100644 index 00000000..defc50a2 --- /dev/null +++ b/docs/examples/kotlin/documentsdb/upsert-document.md @@ -0,0 +1,22 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.DocumentsDB +import io.appwrite.Permission +import io.appwrite.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val documentsDB = DocumentsDB(client) + +val result = documentsDB.upsertDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( "a" to "b" ), // (optional) + permissions = listOf(Permission.read(Role.any())), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/storage/create-file.md b/docs/examples/kotlin/storage/create-file.md index 44d3ac8b..0e095d2d 100644 --- a/docs/examples/kotlin/storage/create-file.md +++ b/docs/examples/kotlin/storage/create-file.md @@ -17,6 +17,6 @@ val result = storage.createFile( fileId = "", file = InputFile.fromPath("file.png"), permissions = listOf(Permission.read(Role.any())), // (optional) - folder = "", // (optional) + folder = "photos/2026", // (optional) ) ``` diff --git a/docs/examples/kotlin/storage/get-file-preview.md b/docs/examples/kotlin/storage/get-file-preview.md index 2bb8cffd..aa83fe6e 100644 --- a/docs/examples/kotlin/storage/get-file-preview.md +++ b/docs/examples/kotlin/storage/get-file-preview.md @@ -19,11 +19,11 @@ val result = storage.getFilePreview( gravity = ImageGravity.CENTER, // (optional) quality = -1, // (optional) borderWidth = 0, // (optional) - borderColor = "", // (optional) + borderColor = "FFFFFF", // (optional) borderRadius = 0, // (optional) opacity = 0, // (optional) rotation = -360, // (optional) - background = "", // (optional) + background = "FFFFFF", // (optional) output = ImageFormat.JPG, // (optional) token = "", // (optional) ) diff --git a/docs/examples/kotlin/tablesdb/decrement-row-column.md b/docs/examples/kotlin/tablesdb/decrement-row-column.md index 848eea97..157c3d11 100644 --- a/docs/examples/kotlin/tablesdb/decrement-row-column.md +++ b/docs/examples/kotlin/tablesdb/decrement-row-column.md @@ -13,8 +13,8 @@ val result = tablesDB.decrementRowColumn( databaseId = "", tableId = "", rowId = "", - column = "", - value = 0, // (optional) + column = "", + value = 1, // (optional) min = 0, // (optional) transactionId = "", // (optional) ) diff --git a/docs/examples/kotlin/tablesdb/increment-row-column.md b/docs/examples/kotlin/tablesdb/increment-row-column.md index 85c03791..802c6a2b 100644 --- a/docs/examples/kotlin/tablesdb/increment-row-column.md +++ b/docs/examples/kotlin/tablesdb/increment-row-column.md @@ -13,9 +13,9 @@ val result = tablesDB.incrementRowColumn( databaseId = "", tableId = "", rowId = "", - column = "", - value = 0, // (optional) - max = 0, // (optional) + column = "", + value = 1, // (optional) + max = 100, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/vectorsdb/create-document.md b/docs/examples/kotlin/vectorsdb/create-document.md new file mode 100644 index 00000000..960654fa --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/create-document.md @@ -0,0 +1,27 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB +import io.appwrite.Permission +import io.appwrite.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.createDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( + "embeddings" to listOf(0.12, -0.55, 0.88, 1.02), + "metadata" to mapOf( + "key" to "value" + ) + ), + permissions = listOf(Permission.read(Role.any())), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/create-operations.md b/docs/examples/kotlin/vectorsdb/create-operations.md new file mode 100644 index 00000000..1a2908af --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/create-operations.md @@ -0,0 +1,24 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.createOperations( + transactionId = "", + operations = listOf(mapOf( + "action" to "create", + "databaseId" to "", + "collectionId" to "", + "documentId" to "", + "data" to mapOf( + "name" to "Walter O'Brien" + ) + )), // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/create-query.md b/docs/examples/kotlin/vectorsdb/create-query.md new file mode 100644 index 00000000..0e8aaf25 --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/create-query.md @@ -0,0 +1,20 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.createQuery( + databaseId = "", + collectionId = "", + queries = listOf(), // (optional) + transactionId = "", // (optional) + total = false, // (optional) + ttl = 0, // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/create-transaction.md b/docs/examples/kotlin/vectorsdb/create-transaction.md new file mode 100644 index 00000000..9ca3de5c --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/create-transaction.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.createTransaction( + ttl = 60, // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/delete-document.md b/docs/examples/kotlin/vectorsdb/delete-document.md new file mode 100644 index 00000000..9cc1856f --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/delete-document.md @@ -0,0 +1,18 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.deleteDocument( + databaseId = "", + collectionId = "", + documentId = "", + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/delete-transaction.md b/docs/examples/kotlin/vectorsdb/delete-transaction.md new file mode 100644 index 00000000..e97ab53a --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/delete-transaction.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.deleteTransaction( + transactionId = "", +) +``` diff --git a/docs/examples/kotlin/vectorsdb/get-document.md b/docs/examples/kotlin/vectorsdb/get-document.md new file mode 100644 index 00000000..22584ab1 --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/get-document.md @@ -0,0 +1,19 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.getDocument( + databaseId = "", + collectionId = "", + documentId = "", + queries = listOf(), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/get-transaction.md b/docs/examples/kotlin/vectorsdb/get-transaction.md new file mode 100644 index 00000000..2a510ea8 --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/get-transaction.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.getTransaction( + transactionId = "", +) +``` diff --git a/docs/examples/kotlin/vectorsdb/list-documents.md b/docs/examples/kotlin/vectorsdb/list-documents.md new file mode 100644 index 00000000..90a7f04a --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/list-documents.md @@ -0,0 +1,20 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.listDocuments( + databaseId = "", + collectionId = "", + queries = listOf(), // (optional) + transactionId = "", // (optional) + total = false, // (optional) + ttl = 0, // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/list-transactions.md b/docs/examples/kotlin/vectorsdb/list-transactions.md new file mode 100644 index 00000000..97981c01 --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/list-transactions.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.listTransactions( + queries = listOf(), // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/update-document.md b/docs/examples/kotlin/vectorsdb/update-document.md new file mode 100644 index 00000000..13422596 --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/update-document.md @@ -0,0 +1,22 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB +import io.appwrite.Permission +import io.appwrite.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.updateDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( "a" to "b" ), // (optional) + permissions = listOf(Permission.read(Role.any())), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/update-transaction.md b/docs/examples/kotlin/vectorsdb/update-transaction.md new file mode 100644 index 00000000..c49da348 --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/update-transaction.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.updateTransaction( + transactionId = "", + commit = false, // (optional) + rollback = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/vectorsdb/upsert-document.md b/docs/examples/kotlin/vectorsdb/upsert-document.md new file mode 100644 index 00000000..111b61c6 --- /dev/null +++ b/docs/examples/kotlin/vectorsdb/upsert-document.md @@ -0,0 +1,22 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.VectorsDB +import io.appwrite.Permission +import io.appwrite.Role + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val vectorsDB = VectorsDB(client) + +val result = vectorsDB.upsertDocument( + databaseId = "", + collectionId = "", + documentId = "", + data = mapOf( "a" to "b" ), // (optional) + permissions = listOf(Permission.read(Role.any())), // (optional) + transactionId = "", // (optional) +) +``` diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 2a7b4801..8f1ca9b8 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -9,7 +9,7 @@ android { defaultConfig { applicationId = "io.appwrite.android" - minSdk = 23 + minSdk = 24 @Suppress("DEPRECATION") targetSdk = 37 versionCode = 1 @@ -41,9 +41,9 @@ dependencies { implementation("androidx.appcompat:appcompat:1.8.0") implementation("com.google.android.material:material:1.14.0") implementation("androidx.constraintlayout:constraintlayout:2.2.2") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.8") + implementation("androidx.navigation:navigation-fragment-ktx:2.10.0") implementation("androidx.fragment:fragment-ktx:1.9.0") - implementation("androidx.navigation:navigation-ui-ktx:2.9.8") + implementation("androidx.navigation:navigation-ui-ktx:2.10.0") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.11.0") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 55f4113a..8b5455ea 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -45,7 +45,9 @@ import kotlin.coroutines.resume class Client @JvmOverloads constructor( context: Context, + @set:JvmSynthetic var endpoint: String = "https://cloud.appwrite.io/v1", + @set:JvmSynthetic var endpointRealtime: String? = null, private var selfSigned: Boolean = false ) : CoroutineScope { @@ -94,8 +96,8 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "27.0.0", - "x-appwrite-response-format" to "1.9.6" + "x-sdk-version" to "27.2.0", + "x-appwrite-response-format" to "2.0.0" ) config = mutableMapOf() diff --git a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt index bc039175..e71acdf2 100644 --- a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt +++ b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt @@ -21,6 +21,8 @@ enum class OAuthProvider(val value: String) { BITLY("bitly"), @SerializedName("box") BOX("box"), + @SerializedName("cloudflare") + CLOUDFLARE("cloudflare"), @SerializedName("dailymotion") DAILYMOTION("dailymotion"), @SerializedName("discord") @@ -65,6 +67,8 @@ enum class OAuthProvider(val value: String) { PAYPALSANDBOX("paypalSandbox"), @SerializedName("podio") PODIO("podio"), + @SerializedName("resend") + RESEND("resend"), @SerializedName("salesforce") SALESFORCE("salesforce"), @SerializedName("slack") diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index 495a4e3a..fd2a009e 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -1633,7 +1633,7 @@ class Account(client: Client) : Service(client) { * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -2098,7 +2098,7 @@ class Account(client: Client) : Service(client) { * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. diff --git a/library/src/main/java/io/appwrite/services/Avatars.kt b/library/src/main/java/io/appwrite/services/Avatars.kt index 7541466c..cce2d3da 100644 --- a/library/src/main/java/io/appwrite/services/Avatars.kt +++ b/library/src/main/java/io/appwrite/services/Avatars.kt @@ -240,13 +240,18 @@ class Avatars(client: Client) : Service(client) { } /** - * Returns the best available profile photo for the currently authenticated user. The endpoint tries each source in priority order and returns the first successful result: Gravatar, Libavatar, Appwrite Initials, built-in static fallback file. + * Returns the best available profile photo for a user. The endpoint tries each source in priority order and returns the first successful result: OAuth2 identity photo, Gravatar, Libravatar, Appwrite Initials, built-in static fallback. + * + * Passing `userId` — `current()` for the authenticated user — resolves the photo from everything known about that user: identity photos, email, and name. An explicit `emailHash` or `name` then overrides just that value, and the user's remaining sources stay in the chain. Without `userId`, passing `emailHash` and/or `name` resolves the avatar from those values alone: the hash is looked up on Gravatar and Libravatar, the name is rendered as initials, and the session user stays out of the chain so their own photo never shadows the avatar being asked for. When nothing is passed, the photo resolves for the currently authenticated user. Emails are only ever accepted pre-hashed, so no address ends up in a URL. * * @param width Output image width in pixels. Pass an integer between 0 and 2000. Defaults to 256. * @param height Output image height in pixels. Pass an integer between 0 and 2000. Defaults to 256. * @param quality Output image quality between 0 and 100. Defaults to 100. * @param output Output image format. Defaults to 'png'. * @param rating Maximum image rating to fetch from Gravatar/Libravatar. Defaults to 'g'. + * @param userId User ID to resolve the photo for. Pass 'current()' for the currently authenticated user. When omitted, the session user is used only if no emailHash and no name is passed. + * @param emailHash SHA256 hash of the lowercase, trimmed email address to look up on Gravatar and Libravatar instead of the user's own email. Pass the hash, never the address itself. + * @param name Name to render initials from instead of the user's own name. Max length: 128 chars. * @return [ByteArray] */ @JvmOverloads @@ -256,6 +261,9 @@ class Avatars(client: Client) : Service(client) { quality: Long? = null, output: String? = null, rating: String? = null, + userId: String? = null, + emailHash: String? = null, + name: String? = null, ): ByteArray { val apiPath = "/avatars/photo" val apiParams = mutableMapOf( @@ -264,6 +272,9 @@ class Avatars(client: Client) : Service(client) { "quality" to quality, "output" to output, "rating" to rating, + "userId" to userId, + "emailHash" to emailHash, + "name" to name, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/main/java/io/appwrite/services/DocumentsDB.kt b/library/src/main/java/io/appwrite/services/DocumentsDB.kt new file mode 100644 index 00000000..28646d98 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/DocumentsDB.kt @@ -0,0 +1,882 @@ +package io.appwrite.services + +import android.net.Uri +import io.appwrite.Client +import io.appwrite.Service +import io.appwrite.exceptions.AppwriteException +import io.appwrite.extensions.classOf +import io.appwrite.models.* +import okhttp3.Cookie +import java.io.File + +/** + * */ +class DocumentsDB(client: Client) : Service(client) { + /** + * List transactions across all databases. + * + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @return [io.appwrite.models.TransactionList] + */ + @JvmOverloads + suspend fun listTransactions( + queries: List? = null, + ): io.appwrite.models.TransactionList { + val apiPath = "/documentsdb/transactions" + val apiParams = mutableMapOf( + "queries" to queries, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.TransactionList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.TransactionList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.TransactionList::class.java, + converter, + ) + } + + /** + * Create a new transaction. + * + * @param ttl Seconds before the transaction expires. + * @return [io.appwrite.models.Transaction] + */ + @JvmOverloads + suspend fun createTransaction( + ttl: Long? = null, + ): io.appwrite.models.Transaction { + val apiPath = "/documentsdb/transactions" + val apiParams = mutableMapOf( + "ttl" to ttl, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Get a transaction by its unique ID. + * + * @param transactionId Transaction ID. + * @return [io.appwrite.models.Transaction] + */ + suspend fun getTransaction( + transactionId: String, + ): io.appwrite.models.Transaction { + val apiPath = ("/documentsdb/transactions/{transactionId}" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Update a transaction, to either commit or roll back its operations. + * + * @param transactionId Transaction ID. + * @param commit Commit transaction? + * @param rollback Rollback transaction? + * @return [io.appwrite.models.Transaction] + */ + @JvmOverloads + suspend fun updateTransaction( + transactionId: String, + commit: Boolean? = null, + rollback: Boolean? = null, + ): io.appwrite.models.Transaction { + val apiPath = ("/documentsdb/transactions/{transactionId}" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf( + "commit" to commit, + "rollback" to rollback, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Delete a transaction by its unique ID. + * + * @param transactionId Transaction ID. + * @return [Any] + */ + suspend fun deleteTransaction( + transactionId: String, + ): Any { + val apiPath = ("/documentsdb/transactions/{transactionId}" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Create multiple operations in a single transaction. + * + * @param transactionId Transaction ID. + * @param operations Array of staged operations. + * @return [io.appwrite.models.Transaction] + */ + @JvmOverloads + suspend fun createOperations( + transactionId: String, + operations: List>? = null, + ): io.appwrite.models.Transaction { + val apiPath = ("/documentsdb/transactions/{transactionId}/operations" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf( + "operations" to operations, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Get a list of all the user's documents in a given collection. You can use the query params to filter your results. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @param ttl TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours). + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + suspend fun listDocuments( + databaseId: String, + collectionId: String, + queries: List? = null, + transactionId: String? = null, + total: Boolean? = null, + ttl: Long? = null, + nestedType: Class, + ): io.appwrite.models.DocumentList { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "transactionId" to transactionId, + "total" to total, + "ttl" to ttl, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.DocumentList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.DocumentList.from(map = it as Map, nestedType) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Get a list of all the user's documents in a given collection. You can use the query params to filter your results. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @param ttl TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours). + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun listDocuments( + databaseId: String, + collectionId: String, + queries: List? = null, + transactionId: String? = null, + total: Boolean? = null, + ttl: Long? = null, + ): io.appwrite.models.DocumentList> = listDocuments( + databaseId, + collectionId, + queries, + transactionId, + total, + ttl, + nestedType = classOf(), + ) + + /** + * Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents. + * @param documentId Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + * @param data Document data as JSON object. + * @param permissions An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun createDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map, + permissions: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + ) + val apiParams = mutableMapOf( + "documentId" to documentId, + "data" to data, + "permissions" to permissions, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents. + * @param documentId Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + * @param data Document data as JSON object. + * @param permissions An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun createDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map, + permissions: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = createDocument( + databaseId, + collectionId, + documentId, + data, + permissions, + transactionId, + nestedType = classOf(), + ) + + /** + * Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents. + * @param documents Array of documents data as JSON objects. + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + suspend fun createDocuments( + databaseId: String, + collectionId: String, + documents: List>, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.DocumentList { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + ) + val apiParams = mutableMapOf( + "documents" to documents, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.DocumentList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.DocumentList.from(map = it as Map, nestedType) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents. + * @param documents Array of documents data as JSON objects. + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun createDocuments( + databaseId: String, + collectionId: String, + documents: List>, + transactionId: String? = null, + ): io.appwrite.models.DocumentList> = createDocuments( + databaseId, + collectionId, + documents, + transactionId, + nestedType = classOf(), + ) + + /** + * Get a document by its unique ID. This endpoint response returns a JSON object with the document data. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param documentId Document ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun getDocument( + databaseId: String, + collectionId: String, + documentId: String, + queries: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Get a document by its unique ID. This endpoint response returns a JSON object with the document data. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param documentId Document ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun getDocument( + databaseId: String, + collectionId: String, + documentId: String, + queries: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = getDocument( + databaseId, + collectionId, + documentId, + queries, + transactionId, + nestedType = classOf(), + ) + + /** + * Create or update a Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include all required fields of the document to be created or updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun upsertDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "data" to data, + "permissions" to permissions, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Create or update a Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include all required fields of the document to be created or updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun upsertDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = upsertDocument( + databaseId, + collectionId, + documentId, + data, + permissions, + transactionId, + nestedType = classOf(), + ) + + /** + * Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include only fields and value pairs to be updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun updateDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "data" to data, + "permissions" to permissions, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include only fields and value pairs to be updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun updateDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = updateDocument( + databaseId, + collectionId, + documentId, + data, + permissions, + transactionId, + nestedType = classOf(), + ) + + /** + * Delete a document by its unique ID. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param documentId Document ID. + * @param transactionId Transaction ID for staging the operation. + * @return [Any] + */ + @JvmOverloads + suspend fun deleteDocument( + databaseId: String, + collectionId: String, + documentId: String, + transactionId: String? = null, + ): Any { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Decrement a specific column of a row by a given value. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param attribute Attribute key. + * @param value Value to decrement the attribute by. The value must be a number. + * @param min Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown. + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun decrementDocumentAttribute( + databaseId: String, + collectionId: String, + documentId: String, + attribute: String, + value: Double? = null, + min: Double? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}/{attribute}/decrement" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + .replace("{attribute}", attribute) + ) + val apiParams = mutableMapOf( + "value" to value, + "min" to min, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Decrement a specific column of a row by a given value. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param attribute Attribute key. + * @param value Value to decrement the attribute by. The value must be a number. + * @param min Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown. + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun decrementDocumentAttribute( + databaseId: String, + collectionId: String, + documentId: String, + attribute: String, + value: Double? = null, + min: Double? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = decrementDocumentAttribute( + databaseId, + collectionId, + documentId, + attribute, + value, + min, + transactionId, + nestedType = classOf(), + ) + + /** + * Increment a specific column of a row by a given value. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param attribute Attribute key. + * @param value Value to increment the attribute by. The value must be a number. + * @param max Maximum value for the attribute. If the current value is greater than this value, an error will be thrown. + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun incrementDocumentAttribute( + databaseId: String, + collectionId: String, + documentId: String, + attribute: String, + value: Double? = null, + max: Double? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}/{attribute}/increment" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + .replace("{attribute}", attribute) + ) + val apiParams = mutableMapOf( + "value" to value, + "max" to max, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Increment a specific column of a row by a given value. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param attribute Attribute key. + * @param value Value to increment the attribute by. The value must be a number. + * @param max Maximum value for the attribute. If the current value is greater than this value, an error will be thrown. + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun incrementDocumentAttribute( + databaseId: String, + collectionId: String, + documentId: String, + attribute: String, + value: Double? = null, + max: Double? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = incrementDocumentAttribute( + databaseId, + collectionId, + documentId, + attribute, + value, + max, + transactionId, + nestedType = classOf(), + ) +} diff --git a/library/src/main/java/io/appwrite/services/Realtime.kt b/library/src/main/java/io/appwrite/services/Realtime.kt index 60e6f41a..01987db8 100644 --- a/library/src/main/java/io/appwrite/services/Realtime.kt +++ b/library/src/main/java/io/appwrite/services/Realtime.kt @@ -19,6 +19,7 @@ import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.CoroutineContext @@ -49,6 +50,34 @@ class Realtime(client: Client) : Service(client), CoroutineScope { private val subscriptionLock = Any() private val presenceLock = Any() + + private val onErrorCallbacks = CopyOnWriteArrayList<(Throwable?, Int?) -> Unit>() + private val onCloseCallbacks = CopyOnWriteArrayList<() -> Unit>() + private val onOpenCallbacks = CopyOnWriteArrayList<() -> Unit>() + } + + fun onError(callback: (error: Throwable?, statusCode: Int?) -> Unit) { + onErrorCallbacks.add(callback) + } + + fun onClose(callback: () -> Unit) { + onCloseCallbacks.add(callback) + } + + fun onOpen(callback: () -> Unit) { + onOpenCallbacks.add(callback) + } + + private fun dispatchError(error: Throwable?, statusCode: Int?) { + onErrorCallbacks.forEach { runCatching { it(error, statusCode) } } + } + + private fun dispatchClose() { + onCloseCallbacks.forEach { runCatching { it() } } + } + + private fun dispatchOpen() { + onOpenCallbacks.forEach { runCatching { it() } } } private fun createSocket() { @@ -144,6 +173,9 @@ class Realtime(client: Client) : Service(client), CoroutineScope { pendingPresence = null } appConnected = false + onErrorCallbacks.clear() + onCloseCallbacks.clear() + onOpenCallbacks.clear() } private fun sendPendingSubscribes() { @@ -390,6 +422,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { if (isStale(webSocket)) return reconnectAttempts = 0 startHeartbeat() + dispatchOpen() } override fun onMessage(webSocket: WebSocket, text: String) { @@ -398,12 +431,17 @@ class Realtime(client: Client) : Service(client), CoroutineScope { launch(IO) { if (isStale(webSocket)) return@launch - val message = text.fromJson() - when (message.type) { - TYPE_ERROR -> handleResponseError(message) - TYPE_CONNECTED -> handleResponseConnected(message) - TYPE_EVENT -> handleResponseEvent(message) - TYPE_PONG -> {} + try { + val message = text.fromJson() + when (message.type) { + TYPE_ERROR -> handleResponseError(message) + TYPE_CONNECTED -> handleResponseConnected(message) + TYPE_EVENT -> handleResponseEvent(message) + TYPE_PONG -> {} + } + } catch (e: Throwable) { + val statusCode = (e as? AppwriteException)?.code + dispatchError(e, statusCode) } } } @@ -459,6 +497,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { synchronized(subscriptionLock) { if (socket === webSocket) socket = null } + dispatchClose() if (!reconnect || code == RealtimeCode.POLICY_VIOLATION.value) { reconnect = true return @@ -490,7 +529,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { synchronized(subscriptionLock) { if (socket === webSocket) socket = null } - t.printStackTrace() + dispatchError(t, null) } } } diff --git a/library/src/main/java/io/appwrite/services/VectorsDB.kt b/library/src/main/java/io/appwrite/services/VectorsDB.kt new file mode 100644 index 00000000..47c4d435 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/VectorsDB.kt @@ -0,0 +1,724 @@ +package io.appwrite.services + +import android.net.Uri +import io.appwrite.Client +import io.appwrite.Service +import io.appwrite.exceptions.AppwriteException +import io.appwrite.extensions.classOf +import io.appwrite.models.* +import okhttp3.Cookie +import java.io.File + +/** + * */ +class VectorsDB(client: Client) : Service(client) { + /** + * List transactions across all databases. + * + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @return [io.appwrite.models.TransactionList] + */ + @JvmOverloads + suspend fun listTransactions( + queries: List? = null, + ): io.appwrite.models.TransactionList { + val apiPath = "/vectorsdb/transactions" + val apiParams = mutableMapOf( + "queries" to queries, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.TransactionList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.TransactionList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.TransactionList::class.java, + converter, + ) + } + + /** + * Create a new transaction. + * + * @param ttl Seconds before the transaction expires. + * @return [io.appwrite.models.Transaction] + */ + @JvmOverloads + suspend fun createTransaction( + ttl: Long? = null, + ): io.appwrite.models.Transaction { + val apiPath = "/vectorsdb/transactions" + val apiParams = mutableMapOf( + "ttl" to ttl, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Get a transaction by its unique ID. + * + * @param transactionId Transaction ID. + * @return [io.appwrite.models.Transaction] + */ + suspend fun getTransaction( + transactionId: String, + ): io.appwrite.models.Transaction { + val apiPath = ("/vectorsdb/transactions/{transactionId}" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Update a transaction, to either commit or roll back its operations. + * + * @param transactionId Transaction ID. + * @param commit Commit transaction? + * @param rollback Rollback transaction? + * @return [io.appwrite.models.Transaction] + */ + @JvmOverloads + suspend fun updateTransaction( + transactionId: String, + commit: Boolean? = null, + rollback: Boolean? = null, + ): io.appwrite.models.Transaction { + val apiPath = ("/vectorsdb/transactions/{transactionId}" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf( + "commit" to commit, + "rollback" to rollback, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Delete a transaction by its unique ID. + * + * @param transactionId Transaction ID. + * @return [Any] + */ + suspend fun deleteTransaction( + transactionId: String, + ): Any { + val apiPath = ("/vectorsdb/transactions/{transactionId}" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Create multiple operations in a single transaction. + * + * @param transactionId Transaction ID. + * @param operations Array of staged operations. + * @return [io.appwrite.models.Transaction] + */ + @JvmOverloads + suspend fun createOperations( + transactionId: String, + operations: List>? = null, + ): io.appwrite.models.Transaction { + val apiPath = ("/vectorsdb/transactions/{transactionId}/operations" + .replace("{transactionId}", transactionId) + ) + val apiParams = mutableMapOf( + "operations" to operations, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Transaction = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Transaction.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Transaction::class.java, + converter, + ) + } + + /** + * Get a list of all the user's documents in a given collection. You can use the query params to filter your results. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 524288 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @param ttl TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours). + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + suspend fun listDocuments( + databaseId: String, + collectionId: String, + queries: List? = null, + transactionId: String? = null, + total: Boolean? = null, + ttl: Long? = null, + nestedType: Class, + ): io.appwrite.models.DocumentList { + val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "transactionId" to transactionId, + "total" to total, + "ttl" to ttl, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.DocumentList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.DocumentList.from(map = it as Map, nestedType) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Get a list of all the user's documents in a given collection. You can use the query params to filter your results. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 524288 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @param ttl TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours). + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun listDocuments( + databaseId: String, + collectionId: String, + queries: List? = null, + transactionId: String? = null, + total: Boolean? = null, + ttl: Long? = null, + ): io.appwrite.models.DocumentList> = listDocuments( + databaseId, + collectionId, + queries, + transactionId, + total, + ttl, + nestedType = classOf(), + ) + + /** + * Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents. + * @param documentId Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + * @param data Document data as JSON object. + * @param permissions An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun createDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map, + permissions: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + ) + val apiParams = mutableMapOf( + "documentId" to documentId, + "data" to data, + "permissions" to permissions, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents. + * @param documentId Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + * @param data Document data as JSON object. + * @param permissions An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun createDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map, + permissions: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = createDocument( + databaseId, + collectionId, + documentId, + data, + permissions, + transactionId, + nestedType = classOf(), + ) + + /** + * Get a list of all the user's documents in a given collection using a POST request. This behaves identically to the list documents endpoint but accepts the queries in the request body, allowing much larger `queries` arrays than can fit in a URL query string. + * + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 524288 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @param ttl TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours). + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + suspend fun createQuery( + databaseId: String, + collectionId: String, + queries: List? = null, + transactionId: String? = null, + total: Boolean? = null, + ttl: Long? = null, + nestedType: Class, + ): io.appwrite.models.DocumentList { + val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/query" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "transactionId" to transactionId, + "total" to total, + "ttl" to ttl, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.DocumentList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.DocumentList.from(map = it as Map, nestedType) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Get a list of all the user's documents in a given collection using a POST request. This behaves identically to the list documents endpoint but accepts the queries in the request body, allowing much larger `queries` arrays than can fit in a URL query string. + * + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 524288 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @param ttl TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours). + * @return [io.appwrite.models.DocumentList] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun createQuery( + databaseId: String, + collectionId: String, + queries: List? = null, + transactionId: String? = null, + total: Boolean? = null, + ttl: Long? = null, + ): io.appwrite.models.DocumentList> = createQuery( + databaseId, + collectionId, + queries, + transactionId, + total, + ttl, + nestedType = classOf(), + ) + + /** + * Get a document by its unique ID. This endpoint response returns a JSON object with the document data. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param documentId Document ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun getDocument( + databaseId: String, + collectionId: String, + documentId: String, + queries: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Get a document by its unique ID. This endpoint response returns a JSON object with the document data. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param documentId Document ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param transactionId Transaction ID to read uncommitted changes within the transaction. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun getDocument( + databaseId: String, + collectionId: String, + documentId: String, + queries: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = getDocument( + databaseId, + collectionId, + documentId, + queries, + transactionId, + nestedType = classOf(), + ) + + /** + * Create or update a Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include all required fields of the document to be created or updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun upsertDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "data" to data, + "permissions" to permissions, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Create or update a Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#documentsDBCreateCollection) API or directly from your database console. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include all required fields of the document to be created or updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun upsertDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = upsertDocument( + databaseId, + collectionId, + documentId, + data, + permissions, + transactionId, + nestedType = classOf(), + ) + + /** + * Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include only fields and value pairs to be updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + suspend fun updateDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + nestedType: Class, + ): io.appwrite.models.Document { + val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "data" to data, + "permissions" to permissions, + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Document = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Document.from(map = it as Map, nestedType) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + ) + } + + /** + * Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. + * @param documentId Document ID. + * @param data Document data as JSON object. Include only fields and value pairs to be updated. + * @param permissions An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions). + * @param transactionId Transaction ID for staging the operation. + * @return [io.appwrite.models.Document] + */ + @JvmOverloads + @Throws(AppwriteException::class) + suspend fun updateDocument( + databaseId: String, + collectionId: String, + documentId: String, + data: Map? = null, + permissions: List? = null, + transactionId: String? = null, + ): io.appwrite.models.Document> = updateDocument( + databaseId, + collectionId, + documentId, + data, + permissions, + transactionId, + nestedType = classOf(), + ) + + /** + * Delete a document by its unique ID. + * + * @param databaseId Database ID. + * @param collectionId Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). + * @param documentId Document ID. + * @param transactionId Transaction ID for staging the operation. + * @return [Any] + */ + @JvmOverloads + suspend fun deleteDocument( + databaseId: String, + collectionId: String, + documentId: String, + transactionId: String? = null, + ): Any { + val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" + .replace("{databaseId}", databaseId) + .replace("{collectionId}", collectionId) + .replace("{documentId}", documentId) + ) + val apiParams = mutableMapOf( + "transactionId" to transactionId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } +} diff --git a/library/src/test/java/io/appwrite/ClientJavaInteropTest.java b/library/src/test/java/io/appwrite/ClientJavaInteropTest.java new file mode 100644 index 00000000..3019bb94 --- /dev/null +++ b/library/src/test/java/io/appwrite/ClientJavaInteropTest.java @@ -0,0 +1,25 @@ +package io.appwrite; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(AndroidJUnit4.class) +public final class ClientJavaInteropTest { + @Test + public void endpointSettersAreFluentFromJava() { + Context context = ApplicationProvider.getApplicationContext(); + Client client = new Client(context) + .setEndpoint("https://cloud.appwrite.io/v1") + .setEndpointRealtime("wss://cloud.appwrite.io/v1"); + + assertEquals("https://cloud.appwrite.io/v1", client.getEndpoint()); + assertEquals("wss://cloud.appwrite.io/v1", client.getEndpointRealtime()); + } +} From f19e2fad6d15dcb5b9ece73431b845fbeefb053a Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 7 Sep 2026 17:15:22 +0100 Subject: [PATCH 07/12] docs: state the SDK targets Appwrite server version 2.0.x --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac566f68..e8e30481 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![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) -**This SDK targets Appwrite server version 1.9.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-android/releases) when APIs differ. +**This SDK targets Appwrite server version 2.0.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-android/releases) when APIs differ. Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Android SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs) From a438838655e2cbe7cec464c74defabcbcdd91256 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 7 Sep 2026 18:21:17 +0100 Subject: [PATCH 08/12] fix: regenerate with the latest sdk-generator and add auto image gravity --- CHANGELOG.md | 1 + docs/examples/java/storage/get-file-preview.md | 2 +- docs/examples/kotlin/storage/get-file-preview.md | 2 +- library/src/main/java/io/appwrite/enums/ImageGravity.kt | 2 ++ library/src/main/java/io/appwrite/services/Storage.kt | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f5921b..f3aa24a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Added: `DocumentsDB` and `VectorsDB` services for document and vector database access * Added: `userId`, `emailHash` and `name` parameters to `Avatars.getPhoto()` * Added: `CLOUDFLARE` and `RESEND` values to `OAuthProvider` enum +* Added: `AUTO` value to `ImageGravity` enum * Fixed: `Client.setEndpoint` and `setEndpointRealtime` are fluent when called from Java * Updated: `x-appwrite-response-format` header to `2.0.0` diff --git a/docs/examples/java/storage/get-file-preview.md b/docs/examples/java/storage/get-file-preview.md index 023a7604..3d753c44 100644 --- a/docs/examples/java/storage/get-file-preview.md +++ b/docs/examples/java/storage/get-file-preview.md @@ -18,7 +18,7 @@ storage.getFilePreview( "", // fileId 0, // width (optional) 0, // height (optional) - ImageGravity.CENTER, // gravity (optional) + ImageGravity.AUTO, // gravity (optional) -1, // quality (optional) 0, // borderWidth (optional) "FFFFFF", // borderColor (optional) diff --git a/docs/examples/kotlin/storage/get-file-preview.md b/docs/examples/kotlin/storage/get-file-preview.md index aa83fe6e..e2d2851d 100644 --- a/docs/examples/kotlin/storage/get-file-preview.md +++ b/docs/examples/kotlin/storage/get-file-preview.md @@ -16,7 +16,7 @@ val result = storage.getFilePreview( fileId = "", width = 0, // (optional) height = 0, // (optional) - gravity = ImageGravity.CENTER, // (optional) + gravity = ImageGravity.AUTO, // (optional) quality = -1, // (optional) borderWidth = 0, // (optional) borderColor = "FFFFFF", // (optional) diff --git a/library/src/main/java/io/appwrite/enums/ImageGravity.kt b/library/src/main/java/io/appwrite/enums/ImageGravity.kt index 58c51022..21e328e4 100644 --- a/library/src/main/java/io/appwrite/enums/ImageGravity.kt +++ b/library/src/main/java/io/appwrite/enums/ImageGravity.kt @@ -3,6 +3,8 @@ package io.appwrite.enums import com.google.gson.annotations.SerializedName enum class ImageGravity(val value: String) { + @SerializedName("auto") + AUTO("auto"), @SerializedName("center") CENTER("center"), @SerializedName("top-left") diff --git a/library/src/main/java/io/appwrite/services/Storage.kt b/library/src/main/java/io/appwrite/services/Storage.kt index 1cf0810c..7c38e0fa 100644 --- a/library/src/main/java/io/appwrite/services/Storage.kt +++ b/library/src/main/java/io/appwrite/services/Storage.kt @@ -261,7 +261,7 @@ class Storage(client: Client) : Service(client) { * @param fileId File ID * @param width Resize preview image width, Pass an integer between 0 to 4000. * @param height Resize preview image height, Pass an integer between 0 to 4000. - * @param gravity Image crop gravity. Can be one of center,top-left,top,top-right,left,right,bottom-left,bottom,bottom-right + * @param gravity Image crop gravity. Can be one of auto,center,top-left,top,top-right,left,right,bottom-left,bottom,bottom-right * @param quality Preview image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality. * @param borderWidth Preview image border in pixels. Pass an integer between 0 to 100. Defaults to 0. * @param borderColor Preview image border color. Use a valid HEX color, no # is needed for prefix. From ddfdab5958d2c41f161477a247f1e46a313599e2 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 24 Sep 2026 15:40:10 +0100 Subject: [PATCH 09/12] chore: update Android SDK to 28.0.0 --- CHANGELOG.md | 12 + README.md | 8 +- ...gs.md => create-email-verification-otp.md} | 5 +- .../java/account/create-id-token-session.md | 32 ++ .../java/account/create-recovery-otp.md | 27 ++ .../account/update-email-verification-otp.md | 27 ++ .../java/account/update-recovery-otp.md | 28 ++ ...gs.md => create-email-verification-otp.md} | 5 +- .../kotlin/account/create-id-token-session.md | 21 ++ .../kotlin/account/create-recovery-otp.md | 16 + .../account/update-email-verification-otp.md | 16 + .../kotlin/account/update-recovery-otp.md | 17 + example/build.gradle.kts | 4 +- library/build.gradle.kts | 2 +- library/src/main/java/io/appwrite/Client.kt | 19 +- .../java/io/appwrite/enums/IdTokenProvider.kt | 12 + .../java/io/appwrite/enums/OAuthProvider.kt | 4 + .../main/java/io/appwrite/models/Execution.kt | 2 +- .../main/java/io/appwrite/models/Identity.kt | 8 + .../src/main/java/io/appwrite/models/Log.kt | 197 ------------ .../main/java/io/appwrite/models/LogList.kt | 37 --- .../src/main/java/io/appwrite/models/User.kt | 10 + .../main/java/io/appwrite/services/Account.kt | 297 +++++++++++++++--- .../java/io/appwrite/services/Databases.kt | 86 +++++ .../java/io/appwrite/services/DocumentsDB.kt | 92 ++++++ .../java/io/appwrite/services/Functions.kt | 12 + .../java/io/appwrite/services/Messaging.kt | 10 + .../java/io/appwrite/services/Organization.kt | 9 + .../java/io/appwrite/services/Presences.kt | 13 + .../main/java/io/appwrite/services/Storage.kt | 43 +++ .../java/io/appwrite/services/TablesDB.kt | 86 +++++ .../main/java/io/appwrite/services/Teams.kt | 71 +++++ .../java/io/appwrite/services/VectorsDB.kt | 68 ++++ 33 files changed, 993 insertions(+), 303 deletions(-) rename docs/examples/java/account/{list-logs.md => create-email-verification-otp.md} (86%) create mode 100644 docs/examples/java/account/create-id-token-session.md create mode 100644 docs/examples/java/account/create-recovery-otp.md create mode 100644 docs/examples/java/account/update-email-verification-otp.md create mode 100644 docs/examples/java/account/update-recovery-otp.md rename docs/examples/kotlin/account/{list-logs.md => create-email-verification-otp.md} (76%) create mode 100644 docs/examples/kotlin/account/create-id-token-session.md create mode 100644 docs/examples/kotlin/account/create-recovery-otp.md create mode 100644 docs/examples/kotlin/account/update-email-verification-otp.md create mode 100644 docs/examples/kotlin/account/update-recovery-otp.md create mode 100644 library/src/main/java/io/appwrite/enums/IdTokenProvider.kt delete mode 100644 library/src/main/java/io/appwrite/models/Log.kt delete mode 100644 library/src/main/java/io/appwrite/models/LogList.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f3aa24a8..435676ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 28.0.0 + +* Breaking: removed `Account.listLogs` and the `Log`, `LogList` models +* Breaking: removed `Client.setDevKey` +* Breaking: SDK now targets Appwrite 2.3 (`x-appwrite-response-format: 2.3.0`) +* Added: `Account.createIdTokenSession` for native Apple and Google sign-in with `IdTokenProvider` enum +* Added: `Account.createRecoveryOTP` and `Account.updateRecoveryOTP` for code-based password recovery +* Added: `Account.createEmailVerificationOTP` and `Account.updateEmailVerificationOTP` +* Added: `KAKAO` and `TIKTOK` values to `OAuthProvider` +* Added: `passwordPwned` field on `User` and `providerIdToken` field on `Identity` +* Fixed: empty-string required path parameters are rejected instead of sent to the API + ## 27.2.0 * Added: `DocumentsDB` and `VectorsDB` services for document and vector database access diff --git a/README.md b/README.md index e8e30481..c8fb25f0 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ ![Maven Central](https://img.shields.io/maven-central/v/io.appwrite/sdk-for-android.svg?color=green&style=flat-square) ![License](https://img.shields.io/github/license/appwrite/sdk-for-android.svg?style=flat-square) -![Version](https://img.shields.io/badge/api%20version-2.0.0-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-2.3.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) -**This SDK targets Appwrite server version 2.0.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-android/releases) when APIs differ. +**This SDK targets Appwrite server version 2.3.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-android/releases) when APIs differ. Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Android SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs) @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:27.2.0") +implementation("io.appwrite:sdk-for-android:28.0.0") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 27.2.0 + 28.0.0 ``` diff --git a/docs/examples/java/account/list-logs.md b/docs/examples/java/account/create-email-verification-otp.md similarity index 86% rename from docs/examples/java/account/list-logs.md rename to docs/examples/java/account/create-email-verification-otp.md index e8de501d..951b4bd8 100644 --- a/docs/examples/java/account/list-logs.md +++ b/docs/examples/java/account/create-email-verification-otp.md @@ -11,9 +11,8 @@ Client client = new Client(context) Account account = new Account(client); -account.listLogs( - List.of(), // queries (optional) - false, // total (optional) +account.createEmailVerificationOTP( + false, // phrase (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/account/create-id-token-session.md b/docs/examples/java/account/create-id-token-session.md new file mode 100644 index 00000000..457b9fee --- /dev/null +++ b/docs/examples/java/account/create-id-token-session.md @@ -0,0 +1,32 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; +import io.appwrite.enums.IdTokenProvider; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.createIdTokenSession( + IdTokenProvider.APPLE, // provider + "", // idToken + "", // nonce (optional) + "", // accessToken (optional) + 0, // accessTokenExpiry (optional) + "", // name (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/create-recovery-otp.md b/docs/examples/java/account/create-recovery-otp.md new file mode 100644 index 00000000..66c165b8 --- /dev/null +++ b/docs/examples/java/account/create-recovery-otp.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.createRecoveryOTP( + "email@example.com", // email + false, // phrase (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/update-email-verification-otp.md b/docs/examples/java/account/update-email-verification-otp.md new file mode 100644 index 00000000..d66521c8 --- /dev/null +++ b/docs/examples/java/account/update-email-verification-otp.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.updateEmailVerificationOTP( + "", // userId + "", // secret + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/update-recovery-otp.md b/docs/examples/java/account/update-recovery-otp.md new file mode 100644 index 00000000..c16df289 --- /dev/null +++ b/docs/examples/java/account/update-recovery-otp.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Account; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Account account = new Account(client); + +account.updateRecoveryOTP( + "", // userId + "", // secret + "password", // password + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/kotlin/account/list-logs.md b/docs/examples/kotlin/account/create-email-verification-otp.md similarity index 76% rename from docs/examples/kotlin/account/list-logs.md rename to docs/examples/kotlin/account/create-email-verification-otp.md index 0bccbf25..6acba146 100644 --- a/docs/examples/kotlin/account/list-logs.md +++ b/docs/examples/kotlin/account/create-email-verification-otp.md @@ -9,8 +9,7 @@ val client = Client(context) val account = Account(client) -val result = account.listLogs( - queries = listOf(), // (optional) - total = false, // (optional) +val result = account.createEmailVerificationOTP( + phrase = false, // (optional) ) ``` diff --git a/docs/examples/kotlin/account/create-id-token-session.md b/docs/examples/kotlin/account/create-id-token-session.md new file mode 100644 index 00000000..5eb6d0da --- /dev/null +++ b/docs/examples/kotlin/account/create-id-token-session.md @@ -0,0 +1,21 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account +import io.appwrite.enums.IdTokenProvider + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.createIdTokenSession( + provider = IdTokenProvider.APPLE, + idToken = "", + nonce = "", // (optional) + accessToken = "", // (optional) + accessTokenExpiry = 0, // (optional) + name = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/account/create-recovery-otp.md b/docs/examples/kotlin/account/create-recovery-otp.md new file mode 100644 index 00000000..754baa87 --- /dev/null +++ b/docs/examples/kotlin/account/create-recovery-otp.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.createRecoveryOTP( + email = "email@example.com", + phrase = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/account/update-email-verification-otp.md b/docs/examples/kotlin/account/update-email-verification-otp.md new file mode 100644 index 00000000..639605bc --- /dev/null +++ b/docs/examples/kotlin/account/update-email-verification-otp.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.updateEmailVerificationOTP( + userId = "", + secret = "", +) +``` diff --git a/docs/examples/kotlin/account/update-recovery-otp.md b/docs/examples/kotlin/account/update-recovery-otp.md new file mode 100644 index 00000000..00b2ff0a --- /dev/null +++ b/docs/examples/kotlin/account/update-recovery-otp.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val account = Account(client) + +val result = account.updateRecoveryOTP( + userId = "", + secret = "", + password = "password", +) +``` diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 8f1ca9b8..2b339ac0 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -41,9 +41,9 @@ dependencies { implementation("androidx.appcompat:appcompat:1.8.0") implementation("com.google.android.material:material:1.14.0") implementation("androidx.constraintlayout:constraintlayout:2.2.2") - implementation("androidx.navigation:navigation-fragment-ktx:2.10.0") + implementation("androidx.navigation:navigation-fragment-ktx:2.10.1") implementation("androidx.fragment:fragment-ktx:1.9.0") - implementation("androidx.navigation:navigation-ui-ktx:2.10.0") + implementation("androidx.navigation:navigation-ui-ktx:2.10.1") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.11.0") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") diff --git a/library/build.gradle.kts b/library/build.gradle.kts index a526d1f2..45641217 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -94,7 +94,7 @@ dependencies { testImplementation("junit:junit:4.13.2") testImplementation("androidx.test.ext:junit-ktx:1.3.0") testImplementation("androidx.test:core-ktx:1.7.0") - testImplementation("org.robolectric:robolectric:4.16.1") + testImplementation("org.robolectric:robolectric:4.17") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0") } diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 8b5455ea..40431217 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -96,8 +96,8 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "27.2.0", - "x-appwrite-response-format" to "2.0.0" + "x-sdk-version" to "28.0.0", + "x-appwrite-response-format" to "2.3.0" ) config = mutableMapOf() @@ -176,21 +176,6 @@ class Client @JvmOverloads constructor( return this } - /** - * Set DevKey - * - * Your secret dev API key - * - * @param {string} devkey - * - * @return this - */ - fun setDevKey(value: String): Client { - config["devKey"] = value - addHeader("x-appwrite-dev-key", value) - return this - } - /** * Set Cookie * diff --git a/library/src/main/java/io/appwrite/enums/IdTokenProvider.kt b/library/src/main/java/io/appwrite/enums/IdTokenProvider.kt new file mode 100644 index 00000000..fb5d6f12 --- /dev/null +++ b/library/src/main/java/io/appwrite/enums/IdTokenProvider.kt @@ -0,0 +1,12 @@ +package io.appwrite.enums + +import com.google.gson.annotations.SerializedName + +enum class IdTokenProvider(val value: String) { + @SerializedName("apple") + APPLE("apple"), + @SerializedName("google") + GOOGLE("google"); + + override fun toString() = value +} diff --git a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt index e71acdf2..4eb5cbfd 100644 --- a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt +++ b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt @@ -47,6 +47,8 @@ enum class OAuthProvider(val value: String) { GOOGLE("google"), @SerializedName("huggingface") HUGGINGFACE("huggingface"), + @SerializedName("kakao") + KAKAO("kakao"), @SerializedName("keycloak") KEYCLOAK("keycloak"), @SerializedName("kick") @@ -77,6 +79,8 @@ enum class OAuthProvider(val value: String) { SPOTIFY("spotify"), @SerializedName("stripe") STRIPE("stripe"), + @SerializedName("tiktok") + TIKTOK("tiktok"), @SerializedName("tradeshift") TRADESHIFT("tradeshift"), @SerializedName("tradeshiftBox") diff --git a/library/src/main/java/io/appwrite/models/Execution.kt b/library/src/main/java/io/appwrite/models/Execution.kt index 662c48ea..c698d9c6 100644 --- a/library/src/main/java/io/appwrite/models/Execution.kt +++ b/library/src/main/java/io/appwrite/models/Execution.kt @@ -113,7 +113,7 @@ data class Execution( val errors: String, /** - * Resource(function/site) execution duration in seconds. + * Total time the resource(function/site) took to respond, in seconds. */ @SerializedName("duration") val duration: Double, diff --git a/library/src/main/java/io/appwrite/models/Identity.kt b/library/src/main/java/io/appwrite/models/Identity.kt index ab70557a..011c32e4 100644 --- a/library/src/main/java/io/appwrite/models/Identity.kt +++ b/library/src/main/java/io/appwrite/models/Identity.kt @@ -67,6 +67,12 @@ data class Identity( @SerializedName("providerRefreshToken") val providerRefreshToken: String, + /** + * Identity Provider ID token (JWT) from the most recent native sign-in. Empty for identities created through the browser OAuth2 flow. + */ + @SerializedName("providerIdToken") + val providerIdToken: String, + ) { fun toMap(): Map = mapOf( "\$id" to id as Any, @@ -79,6 +85,7 @@ data class Identity( "providerAccessToken" to providerAccessToken as Any, "providerAccessTokenExpiry" to providerAccessTokenExpiry as Any, "providerRefreshToken" to providerRefreshToken as Any, + "providerIdToken" to providerIdToken as Any, ) companion object { @@ -96,6 +103,7 @@ data class Identity( providerAccessToken = map["providerAccessToken"] as String, providerAccessTokenExpiry = map["providerAccessTokenExpiry"] as String, providerRefreshToken = map["providerRefreshToken"] as String, + providerIdToken = map["providerIdToken"] as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Log.kt b/library/src/main/java/io/appwrite/models/Log.kt deleted file mode 100644 index 586841c0..00000000 --- a/library/src/main/java/io/appwrite/models/Log.kt +++ /dev/null @@ -1,197 +0,0 @@ -package io.appwrite.models - -import com.google.gson.annotations.SerializedName -import io.appwrite.extensions.jsonCast - -/** - * Log - */ -data class Log( - /** - * Event name. - */ - @SerializedName("event") - val event: String, - - /** - * User ID of the actor recorded for this log. During impersonation, this is the original impersonator, not the impersonated target user. - */ - @SerializedName("userId") - val userId: String, - - /** - * User email of the actor recorded for this log. During impersonation, this is the original impersonator. - */ - @SerializedName("userEmail") - val userEmail: String, - - /** - * User name of the actor recorded for this log. During impersonation, this is the original impersonator. - */ - @SerializedName("userName") - val userName: String, - - /** - * API mode when event triggered. - */ - @SerializedName("mode") - val mode: String, - - /** - * User type who triggered the audit log. Possible values: user, admin, guest, hidden, keyProject, keyAccount, keyOrganization. - */ - @SerializedName("userType") - val userType: String, - - /** - * IP session in use when the session was created. - */ - @SerializedName("ip") - val ip: String, - - /** - * Log creation date in ISO 8601 format. - */ - @SerializedName("time") - val time: String, - - /** - * Operating system code name. View list of [available options](https://github.com/appwrite/appwrite/blob/master/docs/lists/os.json). - */ - @SerializedName("osCode") - val osCode: String, - - /** - * Operating system name. - */ - @SerializedName("osName") - val osName: String, - - /** - * Operating system version. - */ - @SerializedName("osVersion") - val osVersion: String, - - /** - * Client type. - */ - @SerializedName("clientType") - val clientType: String, - - /** - * Client code name. View list of [available options](https://github.com/appwrite/appwrite/blob/master/docs/lists/clients.json). - */ - @SerializedName("clientCode") - val clientCode: String, - - /** - * Client name. - */ - @SerializedName("clientName") - val clientName: String, - - /** - * Client version. - */ - @SerializedName("clientVersion") - val clientVersion: String, - - /** - * Client engine name. - */ - @SerializedName("clientEngine") - val clientEngine: String, - - /** - * Client engine name. - */ - @SerializedName("clientEngineVersion") - val clientEngineVersion: String, - - /** - * Device name. - */ - @SerializedName("deviceName") - val deviceName: String, - - /** - * Device brand name. - */ - @SerializedName("deviceBrand") - val deviceBrand: String, - - /** - * Device model name. - */ - @SerializedName("deviceModel") - val deviceModel: String, - - /** - * Country two-character ISO 3166-1 alpha code. - */ - @SerializedName("countryCode") - val countryCode: String, - - /** - * Country name. - */ - @SerializedName("countryName") - val countryName: String, - -) { - fun toMap(): Map = mapOf( - "event" to event as Any, - "userId" to userId as Any, - "userEmail" to userEmail as Any, - "userName" to userName as Any, - "mode" to mode as Any, - "userType" to userType as Any, - "ip" to ip as Any, - "time" to time as Any, - "osCode" to osCode as Any, - "osName" to osName as Any, - "osVersion" to osVersion as Any, - "clientType" to clientType as Any, - "clientCode" to clientCode as Any, - "clientName" to clientName as Any, - "clientVersion" to clientVersion as Any, - "clientEngine" to clientEngine as Any, - "clientEngineVersion" to clientEngineVersion as Any, - "deviceName" to deviceName as Any, - "deviceBrand" to deviceBrand as Any, - "deviceModel" to deviceModel as Any, - "countryCode" to countryCode as Any, - "countryName" to countryName as Any, - ) - - companion object { - @Suppress("UNCHECKED_CAST") - fun from( - map: Map, - ) = Log( - event = map["event"] as String, - userId = map["userId"] as String, - userEmail = map["userEmail"] as String, - userName = map["userName"] as String, - mode = map["mode"] as String, - userType = map["userType"] as String, - ip = map["ip"] as String, - time = map["time"] as String, - osCode = map["osCode"] as String, - osName = map["osName"] as String, - osVersion = map["osVersion"] as String, - clientType = map["clientType"] as String, - clientCode = map["clientCode"] as String, - clientName = map["clientName"] as String, - clientVersion = map["clientVersion"] as String, - clientEngine = map["clientEngine"] as String, - clientEngineVersion = map["clientEngineVersion"] as String, - deviceName = map["deviceName"] as String, - deviceBrand = map["deviceBrand"] as String, - deviceModel = map["deviceModel"] as String, - countryCode = map["countryCode"] as String, - countryName = map["countryName"] as String, - ) - } -} diff --git a/library/src/main/java/io/appwrite/models/LogList.kt b/library/src/main/java/io/appwrite/models/LogList.kt deleted file mode 100644 index ef51d7a7..00000000 --- a/library/src/main/java/io/appwrite/models/LogList.kt +++ /dev/null @@ -1,37 +0,0 @@ -package io.appwrite.models - -import com.google.gson.annotations.SerializedName -import io.appwrite.extensions.jsonCast - -/** - * Logs List - */ -data class LogList( - /** - * Total number of logs that matched your query. - */ - @SerializedName("total") - val total: Long, - - /** - * List of logs. - */ - @SerializedName("logs") - val logs: List, - -) { - fun toMap(): Map = mapOf( - "total" to total as Any, - "logs" to logs.map { it.toMap() } as Any, - ) - - companion object { - @Suppress("UNCHECKED_CAST") - fun from( - map: Map, - ) = LogList( - total = (map["total"] as Number).toLong(), - logs = (map["logs"] as List>).map { Log.from(map = it) }, - ) - } -} diff --git a/library/src/main/java/io/appwrite/models/User.kt b/library/src/main/java/io/appwrite/models/User.kt index 473cb7fd..0eea3b00 100644 --- a/library/src/main/java/io/appwrite/models/User.kt +++ b/library/src/main/java/io/appwrite/models/User.kt @@ -121,6 +121,12 @@ data class User( @SerializedName("emailIsCanonical") var emailIsCanonical: Boolean?, + /** + * Whether the password was found in a known data breach the last time it was checked. Null when the password has never been checked. + */ + @SerializedName("passwordPwned") + var passwordPwned: Boolean?, + /** * Phone verification status. */ @@ -184,6 +190,7 @@ data class User( "emailIsDisposable" to emailIsDisposable as Any?, "emailIsCorporate" to emailIsCorporate as Any?, "emailIsCanonical" to emailIsCanonical as Any?, + "passwordPwned" to passwordPwned as Any?, "phoneVerification" to phoneVerification as Any, "mfa" to mfa as Any, "prefs" to prefs.toMap() as Any, @@ -214,6 +221,7 @@ data class User( emailIsDisposable: Boolean?, emailIsCorporate: Boolean?, emailIsCanonical: Boolean?, + passwordPwned: Boolean?, phoneVerification: Boolean, mfa: Boolean, prefs: Preferences>, @@ -241,6 +249,7 @@ data class User( emailIsDisposable, emailIsCorporate, emailIsCanonical, + passwordPwned, phoneVerification, mfa, prefs, @@ -274,6 +283,7 @@ data class User( emailIsDisposable = map["emailIsDisposable"] as? Boolean, emailIsCorporate = map["emailIsCorporate"] as? Boolean, emailIsCanonical = map["emailIsCanonical"] as? Boolean, + passwordPwned = map["passwordPwned"] as? Boolean, phoneVerification = map["phoneVerification"] as Boolean, mfa = map["mfa"] as Boolean, prefs = Preferences.from(map = map["prefs"] as Map, nestedType), diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index fd2a009e..17cdf820 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -165,6 +165,9 @@ class Account(client: Client) : Service(client) { suspend fun getConsent( consentId: String, ): io.appwrite.models.Oauth2Consent { + if (consentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"consentId\"") + } val apiPath = ("/account/consents/{consentId}" .replace("{consentId}", consentId) ) @@ -196,6 +199,9 @@ class Account(client: Client) : Service(client) { suspend fun deleteConsent( consentId: String, ): Any { + if (consentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"consentId\"") + } val apiPath = ("/account/consents/{consentId}" .replace("{consentId}", consentId) ) @@ -228,6 +234,9 @@ class Account(client: Client) : Service(client) { queries: List? = null, total: Boolean? = null, ): io.appwrite.models.Oauth2ConsentTokenList { + if (consentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"consentId\"") + } val apiPath = ("/account/consents/{consentId}/tokens" .replace("{consentId}", consentId) ) @@ -264,6 +273,12 @@ class Account(client: Client) : Service(client) { consentId: String, tokenId: String, ): io.appwrite.models.Oauth2ConsentToken { + if (consentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"consentId\"") + } + if (tokenId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tokenId\"") + } val apiPath = ("/account/consents/{consentId}/tokens/{tokenId}" .replace("{consentId}", consentId) .replace("{tokenId}", tokenId) @@ -298,6 +313,12 @@ class Account(client: Client) : Service(client) { consentId: String, tokenId: String, ): Any { + if (consentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"consentId\"") + } + if (tokenId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tokenId\"") + } val apiPath = ("/account/consents/{consentId}/tokens/{tokenId}" .replace("{consentId}", consentId) .replace("{tokenId}", tokenId) @@ -418,6 +439,9 @@ class Account(client: Client) : Service(client) { suspend fun deleteIdentity( identityId: String, ): Any { + if (identityId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"identityId\"") + } val apiPath = ("/account/identities/{identityId}" .replace("{identityId}", identityId) ) @@ -425,6 +449,7 @@ class Account(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -468,41 +493,6 @@ class Account(client: Client) : Service(client) { ) } - /** - * Get the list of latest security activity logs for the currently logged in user. Each log returns user IP address, location and date and time of log. - * - * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset - * @param total When set to false, the total count returned will be 0 and will not be calculated. - * @return [io.appwrite.models.LogList] - */ - @JvmOverloads - suspend fun listLogs( - queries: List? = null, - total: Boolean? = null, - ): io.appwrite.models.LogList { - val apiPath = "/account/logs" - val apiParams = mutableMapOf( - "queries" to queries, - "total" to total, - ) - val apiHeaders = mutableMapOf( - "X-Appwrite-Project" to client.config["project"].orEmpty(), - "accept" to "application/json", - ) - val converter: (Any) -> io.appwrite.models.LogList = { - @Suppress("UNCHECKED_CAST") - io.appwrite.models.LogList.from(map = it as Map) - } - return client.call( - "GET", - apiPath, - apiHeaders, - apiParams, - responseType = io.appwrite.models.LogList::class.java, - converter, - ) - } - /** * Enable or disable MFA on an account. * @@ -754,6 +744,7 @@ class Account(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -780,6 +771,7 @@ class Account(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -1475,6 +1467,84 @@ class Account(client: Client) : Service(client) { ) } + /** + * Use this endpoint to send a 6-digit password recovery code to the user's email address. Unlike [createRecovery](https://appwrite.io/docs/references/cloud/client-web/account#createRecovery), this method requires no redirect URL, which makes it suitable for mobile and desktop apps that cannot host a recovery page. Learn more about how to [complete the recovery process](https://appwrite.io/docs/references/cloud/client-web/account#updateRecoveryOTP). The code sent to the user's email address is valid for 15 minutes. + * + * Enable the **phrase** parameter to include a randomly generated security phrase in both the email and the response. Showing that phrase in your app lets the user confirm the email genuinely came from your request, which helps protect against phishing. + * + * + * @param email User email. + * @param phrase Toggle for security phrase. If enabled, email will be sent with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow. + * @return [io.appwrite.models.Token] + */ + @JvmOverloads + suspend fun createRecoveryOTP( + email: String, + phrase: Boolean? = null, + ): io.appwrite.models.Token { + val apiPath = "/account/recovery/otp" + val apiParams = mutableMapOf( + "email" to email, + "phrase" to phrase, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Token.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Token::class.java, + converter, + ) + } + + /** + * Use this endpoint to complete the user password recovery process using the 6-digit code that was emailed by [createRecoveryOTP](https://appwrite.io/docs/references/cloud/client-web/account#createRecoveryOTP). Pass the **userId** of the user along with the **secret** code from the email and the new **password** to set. If confirmed, this route will return a 200 status code, the code is consumed and the user's password is updated. + * + * + * @param userId User ID. + * @param secret Valid recovery OTP code. + * @param password New user password. Must be between 8 and 256 chars. + * @return [io.appwrite.models.Token] + */ + suspend fun updateRecoveryOTP( + userId: String, + secret: String, + password: String, + ): io.appwrite.models.Token { + val apiPath = "/account/recovery/otp" + val apiParams = mutableMapOf( + "userId" to userId, + "secret" to secret, + "password" to password, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Token.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Token::class.java, + converter, + ) + } + /** * Get the list of active sessions across different devices for the currently logged in user. * @@ -1512,6 +1582,7 @@ class Account(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -1586,6 +1657,65 @@ class Account(client: Client) : Service(client) { ) } + /** + * Allow the user to login to their account using an OpenID Connect ID token obtained natively from the OAuth2 provider, for example via Google Credential Manager on Android or Sign in with Apple on iOS. No browser or redirect is involved: the ID token is verified against the provider's published signing keys and a session is created in a single request. + * + * Native sign-in is switched on per provider with its nativeEnabled setting. It is independent of the browser-based flow's enabled setting, which has no effect on this endpoint. The token's audience must match the provider's configured client ID or one of its native client IDs; tokens issued for any other client ID are rejected. For Sign in with Apple, register your app's bundle ID as a native client ID. For Google, the web client ID used by Credential Manager is usually the configured client ID; add your Android and iOS client IDs as native client IDs if your app requests tokens for them. + * + * Pass the raw nonce used when requesting the ID token so it can be validated against the token's nonce claim. When signing in with Apple, the nonce is required: hash it with SHA-256 before passing it to the Apple SDK, and send the raw value here - Apple tokens requested without a nonce are rejected. For Google the nonce is optional: it is validated whenever the token carries one, and ignored when the provider issued the token without one. Apple only returns the user's name on the first authorization, and never inside the ID token - capture it on the client and pass it via the name parameter. + * + * If there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the verified email received from the provider and attach the new session to the existing user. If no matching user is found - the server will create a new user. + * + * This flow does not return provider refresh tokens. You may pass an access token the provider handed your client, along with its lifetime, to store it on the session - but Appwrite cannot renew it once it expires. If your app needs long-lived access to provider APIs, use the browser-based OAuth2 flow instead. + * + * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). + * + * + * @param provider OAuth2 provider that issued the ID token. Currently, supported providers are: apple, google. + * @param idToken OpenID Connect ID token (JWT) obtained natively from the provider, for example via Google Credential Manager or Sign in with Apple. + * @param nonce Raw nonce used when requesting the ID token. Required for Apple, and whenever the token carries a nonce claim, which must match it. Ignored when the provider issued the token without a nonce. + * @param accessToken Provider access token to store alongside the session for calling provider APIs. Never used for authentication. + * @param accessTokenExpiry Seconds until the provider access token expires, as reported by the provider. Stored so clients can tell when the stored token goes stale. + * @param name User name. Only used when creating a new user and the ID token has no name claim, such as on the first Sign in with Apple authorization. + * @return [io.appwrite.models.Session] + */ + @JvmOverloads + suspend fun createIdTokenSession( + provider: io.appwrite.enums.IdTokenProvider, + idToken: String, + nonce: String? = null, + accessToken: String? = null, + accessTokenExpiry: Long? = null, + name: String? = null, + ): io.appwrite.models.Session { + val apiPath = "/account/sessions/id-token" + val apiParams = mutableMapOf( + "provider" to provider, + "idToken" to idToken, + "nonce" to nonce, + "accessToken" to accessToken, + "accessTokenExpiry" to accessTokenExpiry, + "name" to name, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Session = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Session.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Session::class.java, + converter, + ) + } + /** * Use this endpoint to create a session from token. Provide the **userId** and **secret** parameters from the successful response of authentication flows initiated by token creation. For example, magic URL and phone login. * @@ -1633,7 +1763,7 @@ class Account(client: Client) : Service(client) { * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -1784,6 +1914,9 @@ class Account(client: Client) : Service(client) { suspend fun getSession( sessionId: String, ): io.appwrite.models.Session { + if (sessionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"sessionId\"") + } val apiPath = ("/account/sessions/{sessionId}" .replace("{sessionId}", sessionId) ) @@ -1815,6 +1948,9 @@ class Account(client: Client) : Service(client) { suspend fun updateSession( sessionId: String, ): io.appwrite.models.Session { + if (sessionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"sessionId\"") + } val apiPath = ("/account/sessions/{sessionId}" .replace("{sessionId}", sessionId) ) @@ -1847,6 +1983,9 @@ class Account(client: Client) : Service(client) { suspend fun deleteSession( sessionId: String, ): Any { + if (sessionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"sessionId\"") + } val apiPath = ("/account/sessions/{sessionId}" .replace("{sessionId}", sessionId) ) @@ -1854,6 +1993,7 @@ class Account(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -1904,7 +2044,7 @@ class Account(client: Client) : Service(client) { ) /** - * Use this endpoint to register a device for push notifications. Provide a target ID (custom or generated using ID.unique()), a device identifier (usually a device token), and optionally specify which provider should send notifications to this target. The target is automatically linked to the current session and includes device information like brand and model. + * Use this endpoint to register a device for push notifications. Provide a target ID (custom or generated using ID.unique()), a device identifier (usually a device token), and optionally specify which provider should send notifications to this target. The target is automatically linked to the current session and includes device information like brand and model. A session holds one push target per provider, so if one already exists this endpoint updates and returns that target instead of creating a second one, and a device that rotates its token is never notified twice. * * @param targetId Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param identifier The target identifier (token, email, phone etc.) @@ -1953,6 +2093,9 @@ class Account(client: Client) : Service(client) { targetId: String, identifier: String, ): io.appwrite.models.Target { + if (targetId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"targetId\"") + } val apiPath = ("/account/targets/{targetId}/push" .replace("{targetId}", targetId) ) @@ -1987,6 +2130,9 @@ class Account(client: Client) : Service(client) { suspend fun deletePushTarget( targetId: String, ): Any { + if (targetId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"targetId\"") + } val apiPath = ("/account/targets/{targetId}/push" .replace("{targetId}", targetId) ) @@ -1994,6 +2140,7 @@ class Account(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -2096,9 +2243,11 @@ class Account(client: Client) : Service(client) { * * If authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint. * + * If there is already an active session, the OAuth2 identity is attached to the logged-in account and that session stays active until the token is exchanged for a new one. + * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -2351,6 +2500,78 @@ class Account(client: Client) : Service(client) { ) } + /** + * Use this endpoint to send a 6-digit verification code to the currently logged in user's email address. Unlike [createEmailVerification](https://appwrite.io/docs/references/cloud/client-web/account#createEmailVerification), this method requires no redirect URL, which makes it suitable for mobile and desktop apps that cannot host a verification page. Learn more about how to [complete the verification process](https://appwrite.io/docs/references/cloud/client-web/account#updateEmailVerificationOTP). The code sent to the user's email address is valid for 15 minutes. + * + * Enable the **phrase** parameter to include a randomly generated security phrase in both the email and the response. Showing that phrase in your app lets the user confirm the email genuinely came from your request, which helps protect against phishing. + * + * + * @param phrase Toggle for security phrase. If enabled, email will be sent with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow. + * @return [io.appwrite.models.Token] + */ + @JvmOverloads + suspend fun createEmailVerificationOTP( + phrase: Boolean? = null, + ): io.appwrite.models.Token { + val apiPath = "/account/verifications/email/otp" + val apiParams = mutableMapOf( + "phrase" to phrase, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Token.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Token::class.java, + converter, + ) + } + + /** + * Use this endpoint to complete the user email verification process using the 6-digit code that was emailed by [createEmailVerificationOTP](https://appwrite.io/docs/references/cloud/client-web/account#createEmailVerificationOTP). Pass the **userId** of the user being verified along with the **secret** code from the email. If confirmed, this route will return a 200 status code and the code is consumed. + * + * + * @param userId User ID. + * @param secret Valid verification OTP code. + * @return [io.appwrite.models.Token] + */ + suspend fun updateEmailVerificationOTP( + userId: String, + secret: String, + ): io.appwrite.models.Token { + val apiPath = "/account/verifications/email/otp" + val apiParams = mutableMapOf( + "userId" to userId, + "secret" to secret, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Token.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Token::class.java, + converter, + ) + } + /** * Use this endpoint to send a verification SMS to the currently logged in user. This endpoint is meant for use after updating a user's phone number using the [accountUpdatePhone](https://appwrite.io/docs/references/cloud/client-web/account#updatePhone) endpoint. Learn more about how to [complete the verification process](https://appwrite.io/docs/references/cloud/client-web/account#updatePhoneVerification). The verification code sent to the user's phone number is valid for 15 minutes. * diff --git a/library/src/main/java/io/appwrite/services/Databases.kt b/library/src/main/java/io/appwrite/services/Databases.kt index a1dda292..fcc85678 100644 --- a/library/src/main/java/io/appwrite/services/Databases.kt +++ b/library/src/main/java/io/appwrite/services/Databases.kt @@ -98,6 +98,9 @@ class Databases(client: Client) : Service(client) { suspend fun getTransaction( transactionId: String, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/databases/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -138,6 +141,9 @@ class Databases(client: Client) : Service(client) { commit: Boolean? = null, rollback: Boolean? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/databases/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -177,6 +183,9 @@ class Databases(client: Client) : Service(client) { suspend fun deleteTransaction( transactionId: String, ): Any { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/databases/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -184,6 +193,7 @@ class Databases(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -210,6 +220,9 @@ class Databases(client: Client) : Service(client) { transactionId: String, operations: List>? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/databases/transactions/{transactionId}/operations" .replace("{transactionId}", transactionId) ) @@ -260,6 +273,12 @@ class Databases(client: Client) : Service(client) { ttl: Long? = null, nestedType: Class, ): io.appwrite.models.DocumentList { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -347,6 +366,12 @@ class Databases(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -433,6 +458,15 @@ class Databases(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -516,6 +550,15 @@ class Databases(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -604,6 +647,15 @@ class Databases(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -687,6 +739,15 @@ class Databases(client: Client) : Service(client) { documentId: String, transactionId: String? = null, ): Any { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -698,6 +759,7 @@ class Databases(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -735,6 +797,18 @@ class Databases(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } + if (attribute.isEmpty()) { + throw AppwriteException("Missing required parameter: \"attribute\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}/{attribute}/decrement" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -829,6 +903,18 @@ class Databases(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } + if (attribute.isEmpty()) { + throw AppwriteException("Missing required parameter: \"attribute\"") + } val apiPath = ("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}/{attribute}/increment" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) diff --git a/library/src/main/java/io/appwrite/services/DocumentsDB.kt b/library/src/main/java/io/appwrite/services/DocumentsDB.kt index 28646d98..fb7cd720 100644 --- a/library/src/main/java/io/appwrite/services/DocumentsDB.kt +++ b/library/src/main/java/io/appwrite/services/DocumentsDB.kt @@ -86,6 +86,9 @@ class DocumentsDB(client: Client) : Service(client) { suspend fun getTransaction( transactionId: String, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/documentsdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -122,6 +125,9 @@ class DocumentsDB(client: Client) : Service(client) { commit: Boolean? = null, rollback: Boolean? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/documentsdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -157,6 +163,9 @@ class DocumentsDB(client: Client) : Service(client) { suspend fun deleteTransaction( transactionId: String, ): Any { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/documentsdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -164,6 +173,7 @@ class DocumentsDB(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -186,6 +196,9 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String, operations: List>? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/documentsdb/transactions/{transactionId}/operations" .replace("{transactionId}", transactionId) ) @@ -232,6 +245,12 @@ class DocumentsDB(client: Client) : Service(client) { ttl: Long? = null, nestedType: Class, ): io.appwrite.models.DocumentList { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -311,6 +330,12 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -387,6 +412,12 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.DocumentList { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -457,6 +488,15 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -532,6 +572,15 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -612,6 +661,15 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -687,6 +745,15 @@ class DocumentsDB(client: Client) : Service(client) { documentId: String, transactionId: String? = null, ): Any { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -698,6 +765,7 @@ class DocumentsDB(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -731,6 +799,18 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } + if (attribute.isEmpty()) { + throw AppwriteException("Missing required parameter: \"attribute\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}/{attribute}/decrement" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -817,6 +897,18 @@ class DocumentsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } + if (attribute.isEmpty()) { + throw AppwriteException("Missing required parameter: \"attribute\"") + } val apiPath = ("/documentsdb/{databaseId}/collections/{collectionId}/documents/{documentId}/{attribute}/increment" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) diff --git a/library/src/main/java/io/appwrite/services/Functions.kt b/library/src/main/java/io/appwrite/services/Functions.kt index b6da2d17..57821693 100644 --- a/library/src/main/java/io/appwrite/services/Functions.kt +++ b/library/src/main/java/io/appwrite/services/Functions.kt @@ -26,6 +26,9 @@ class Functions(client: Client) : Service(client) { queries: List? = null, total: Boolean? = null, ): io.appwrite.models.ExecutionList { + if (functionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"functionId\"") + } val apiPath = ("/functions/{functionId}/executions" .replace("{functionId}", functionId) ) @@ -73,6 +76,9 @@ class Functions(client: Client) : Service(client) { headers: Map? = null, scheduledAt: String? = null, ): io.appwrite.models.Execution { + if (functionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"functionId\"") + } val apiPath = ("/functions/{functionId}/executions" .replace("{functionId}", functionId) ) @@ -114,6 +120,12 @@ class Functions(client: Client) : Service(client) { functionId: String, executionId: String, ): io.appwrite.models.Execution { + if (functionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"functionId\"") + } + if (executionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"executionId\"") + } val apiPath = ("/functions/{functionId}/executions/{executionId}" .replace("{functionId}", functionId) .replace("{executionId}", executionId) diff --git a/library/src/main/java/io/appwrite/services/Messaging.kt b/library/src/main/java/io/appwrite/services/Messaging.kt index 85b03fb2..0e816f78 100644 --- a/library/src/main/java/io/appwrite/services/Messaging.kt +++ b/library/src/main/java/io/appwrite/services/Messaging.kt @@ -25,6 +25,9 @@ class Messaging(client: Client) : Service(client) { subscriberId: String, targetId: String, ): io.appwrite.models.Subscriber { + if (topicId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"topicId\"") + } val apiPath = ("/messaging/topics/{topicId}/subscribers" .replace("{topicId}", topicId) ) @@ -62,6 +65,12 @@ class Messaging(client: Client) : Service(client) { topicId: String, subscriberId: String, ): Any { + if (topicId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"topicId\"") + } + if (subscriberId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"subscriberId\"") + } val apiPath = ("/messaging/topics/{topicId}/subscribers/{subscriberId}" .replace("{topicId}", topicId) .replace("{subscriberId}", subscriberId) @@ -70,6 +79,7 @@ class Messaging(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", diff --git a/library/src/main/java/io/appwrite/services/Organization.kt b/library/src/main/java/io/appwrite/services/Organization.kt index 64b343c2..264a356e 100644 --- a/library/src/main/java/io/appwrite/services/Organization.kt +++ b/library/src/main/java/io/appwrite/services/Organization.kt @@ -92,6 +92,9 @@ class Organization(client: Client) : Service(client) { suspend fun getInstallation( installationId: String, ): io.appwrite.models.AppInstallation { + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } val apiPath = ("/organization/installations/{installationId}" .replace("{installationId}", installationId) ) @@ -126,6 +129,9 @@ class Organization(client: Client) : Service(client) { installationId: String, authorizationDetails: String? = null, ): io.appwrite.models.AppInstallation { + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } val apiPath = ("/organization/installations/{installationId}" .replace("{installationId}", installationId) ) @@ -160,6 +166,9 @@ class Organization(client: Client) : Service(client) { suspend fun deleteInstallation( installationId: String, ): Any { + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } val apiPath = ("/organization/installations/{installationId}" .replace("{installationId}", installationId) ) diff --git a/library/src/main/java/io/appwrite/services/Presences.kt b/library/src/main/java/io/appwrite/services/Presences.kt index 1d22d96d..a28448e4 100644 --- a/library/src/main/java/io/appwrite/services/Presences.kt +++ b/library/src/main/java/io/appwrite/services/Presences.kt @@ -61,6 +61,9 @@ class Presences(client: Client) : Service(client) { suspend fun get( presenceId: String, ): io.appwrite.models.Presence { + if (presenceId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"presenceId\"") + } val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) @@ -102,6 +105,9 @@ class Presences(client: Client) : Service(client) { expiresAt: String? = null, metadata: Map? = null, ): io.appwrite.models.Presence { + if (presenceId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"presenceId\"") + } val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) @@ -151,6 +157,9 @@ class Presences(client: Client) : Service(client) { permissions: List? = null, purge: Boolean? = null, ): io.appwrite.models.Presence { + if (presenceId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"presenceId\"") + } val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) @@ -190,6 +199,9 @@ class Presences(client: Client) : Service(client) { suspend fun delete( presenceId: String, ): Any { + if (presenceId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"presenceId\"") + } val apiPath = ("/presences/{presenceId}" .replace("{presenceId}", presenceId) ) @@ -197,6 +209,7 @@ class Presences(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", diff --git a/library/src/main/java/io/appwrite/services/Storage.kt b/library/src/main/java/io/appwrite/services/Storage.kt index 7c38e0fa..cc4f8608 100644 --- a/library/src/main/java/io/appwrite/services/Storage.kt +++ b/library/src/main/java/io/appwrite/services/Storage.kt @@ -30,6 +30,9 @@ class Storage(client: Client) : Service(client) { search: String? = null, total: Boolean? = null, ): io.appwrite.models.FileList { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files" .replace("{bucketId}", bucketId) ) @@ -82,6 +85,9 @@ class Storage(client: Client) : Service(client) { folder: String? = null, onProgress: ((UploadProgress) -> Unit)? = null ): io.appwrite.models.File { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files" .replace("{bucketId}", bucketId) ) @@ -125,6 +131,12 @@ class Storage(client: Client) : Service(client) { bucketId: String, fileId: String, ): io.appwrite.models.File { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } + if (fileId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"fileId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files/{fileId}" .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) @@ -164,6 +176,12 @@ class Storage(client: Client) : Service(client) { name: String? = null, permissions: List? = null, ): io.appwrite.models.File { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } + if (fileId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"fileId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files/{fileId}" .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) @@ -202,6 +220,12 @@ class Storage(client: Client) : Service(client) { bucketId: String, fileId: String, ): Any { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } + if (fileId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"fileId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files/{fileId}" .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) @@ -210,6 +234,7 @@ class Storage(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -234,6 +259,12 @@ class Storage(client: Client) : Service(client) { fileId: String, token: String? = null, ): ByteArray { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } + if (fileId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"fileId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files/{fileId}/download" .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) @@ -290,6 +321,12 @@ class Storage(client: Client) : Service(client) { output: io.appwrite.enums.ImageFormat? = null, token: String? = null, ): ByteArray { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } + if (fileId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"fileId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files/{fileId}/preview" .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) @@ -335,6 +372,12 @@ class Storage(client: Client) : Service(client) { fileId: String, token: String? = null, ): ByteArray { + if (bucketId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"bucketId\"") + } + if (fileId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"fileId\"") + } val apiPath = ("/storage/buckets/{bucketId}/files/{fileId}/view" .replace("{bucketId}", bucketId) .replace("{fileId}", fileId) diff --git a/library/src/main/java/io/appwrite/services/TablesDB.kt b/library/src/main/java/io/appwrite/services/TablesDB.kt index 0c7554ad..7a8881df 100644 --- a/library/src/main/java/io/appwrite/services/TablesDB.kt +++ b/library/src/main/java/io/appwrite/services/TablesDB.kt @@ -86,6 +86,9 @@ class TablesDB(client: Client) : Service(client) { suspend fun getTransaction( transactionId: String, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/tablesdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -122,6 +125,9 @@ class TablesDB(client: Client) : Service(client) { commit: Boolean? = null, rollback: Boolean? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/tablesdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -157,6 +163,9 @@ class TablesDB(client: Client) : Service(client) { suspend fun deleteTransaction( transactionId: String, ): Any { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/tablesdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -164,6 +173,7 @@ class TablesDB(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -186,6 +196,9 @@ class TablesDB(client: Client) : Service(client) { transactionId: String, operations: List>? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/tablesdb/transactions/{transactionId}/operations" .replace("{transactionId}", transactionId) ) @@ -232,6 +245,12 @@ class TablesDB(client: Client) : Service(client) { ttl: Long? = null, nestedType: Class, ): io.appwrite.models.RowList { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) @@ -311,6 +330,12 @@ class TablesDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Row { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) @@ -389,6 +414,15 @@ class TablesDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Row { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } + if (rowId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"rowId\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows/{rowId}" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) @@ -464,6 +498,15 @@ class TablesDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Row { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } + if (rowId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"rowId\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows/{rowId}" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) @@ -544,6 +587,15 @@ class TablesDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Row { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } + if (rowId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"rowId\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows/{rowId}" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) @@ -619,6 +671,15 @@ class TablesDB(client: Client) : Service(client) { rowId: String, transactionId: String? = null, ): Any { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } + if (rowId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"rowId\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows/{rowId}" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) @@ -630,6 +691,7 @@ class TablesDB(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -663,6 +725,18 @@ class TablesDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Row { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } + if (rowId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"rowId\"") + } + if (column.isEmpty()) { + throw AppwriteException("Missing required parameter: \"column\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows/{rowId}/{column}/decrement" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) @@ -749,6 +823,18 @@ class TablesDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Row { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (tableId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"tableId\"") + } + if (rowId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"rowId\"") + } + if (column.isEmpty()) { + throw AppwriteException("Missing required parameter: \"column\"") + } val apiPath = ("/tablesdb/{databaseId}/tables/{tableId}/rows/{rowId}/{column}/increment" .replace("{databaseId}", databaseId) .replace("{tableId}", tableId) diff --git a/library/src/main/java/io/appwrite/services/Teams.kt b/library/src/main/java/io/appwrite/services/Teams.kt index 9cf474da..7708fcca 100644 --- a/library/src/main/java/io/appwrite/services/Teams.kt +++ b/library/src/main/java/io/appwrite/services/Teams.kt @@ -143,6 +143,9 @@ class Teams(client: Client) : Service(client) { teamId: String, nestedType: Class, ): io.appwrite.models.Team { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}" .replace("{teamId}", teamId) ) @@ -191,6 +194,9 @@ class Teams(client: Client) : Service(client) { name: String, nestedType: Class, ): io.appwrite.models.Team { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}" .replace("{teamId}", teamId) ) @@ -242,6 +248,9 @@ class Teams(client: Client) : Service(client) { suspend fun delete( teamId: String, ): Any { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}" .replace("{teamId}", teamId) ) @@ -249,6 +258,7 @@ class Teams(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -273,6 +283,9 @@ class Teams(client: Client) : Service(client) { queries: List? = null, total: Boolean? = null, ): io.appwrite.models.AppInstallationList { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}/installations" .replace("{teamId}", teamId) ) @@ -312,6 +325,9 @@ class Teams(client: Client) : Service(client) { appId: String, authorizationDetails: String? = null, ): io.appwrite.models.AppInstallation { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}/installations" .replace("{teamId}", teamId) ) @@ -349,6 +365,12 @@ class Teams(client: Client) : Service(client) { teamId: String, installationId: String, ): io.appwrite.models.AppInstallation { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } val apiPath = ("/teams/{teamId}/installations/{installationId}" .replace("{teamId}", teamId) .replace("{installationId}", installationId) @@ -386,6 +408,12 @@ class Teams(client: Client) : Service(client) { installationId: String, authorizationDetails: String? = null, ): io.appwrite.models.AppInstallation { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } val apiPath = ("/teams/{teamId}/installations/{installationId}" .replace("{teamId}", teamId) .replace("{installationId}", installationId) @@ -423,6 +451,12 @@ class Teams(client: Client) : Service(client) { teamId: String, installationId: String, ): Any { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } val apiPath = ("/teams/{teamId}/installations/{installationId}" .replace("{teamId}", teamId) .replace("{installationId}", installationId) @@ -458,6 +492,9 @@ class Teams(client: Client) : Service(client) { search: String? = null, total: Boolean? = null, ): io.appwrite.models.MembershipList { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}/memberships" .replace("{teamId}", teamId) ) @@ -513,6 +550,9 @@ class Teams(client: Client) : Service(client) { url: String? = null, name: String? = null, ): io.appwrite.models.Membership { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}/memberships" .replace("{teamId}", teamId) ) @@ -554,6 +594,12 @@ class Teams(client: Client) : Service(client) { teamId: String, membershipId: String, ): io.appwrite.models.Membership { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } + if (membershipId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"membershipId\"") + } val apiPath = ("/teams/{teamId}/memberships/{membershipId}" .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) @@ -591,6 +637,12 @@ class Teams(client: Client) : Service(client) { membershipId: String, roles: List, ): io.appwrite.models.Membership { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } + if (membershipId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"membershipId\"") + } val apiPath = ("/teams/{teamId}/memberships/{membershipId}" .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) @@ -628,6 +680,12 @@ class Teams(client: Client) : Service(client) { teamId: String, membershipId: String, ): Any { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } + if (membershipId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"membershipId\"") + } val apiPath = ("/teams/{teamId}/memberships/{membershipId}" .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) @@ -636,6 +694,7 @@ class Teams(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -664,6 +723,12 @@ class Teams(client: Client) : Service(client) { userId: String, secret: String, ): io.appwrite.models.Membership { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } + if (membershipId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"membershipId\"") + } val apiPath = ("/teams/{teamId}/memberships/{membershipId}/status" .replace("{teamId}", teamId) .replace("{membershipId}", membershipId) @@ -701,6 +766,9 @@ class Teams(client: Client) : Service(client) { teamId: String, nestedType: Class, ): io.appwrite.models.Preferences { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}/prefs" .replace("{teamId}", teamId) ) @@ -749,6 +817,9 @@ class Teams(client: Client) : Service(client) { prefs: Map, nestedType: Class, ): io.appwrite.models.Preferences { + if (teamId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"teamId\"") + } val apiPath = ("/teams/{teamId}/prefs" .replace("{teamId}", teamId) ) diff --git a/library/src/main/java/io/appwrite/services/VectorsDB.kt b/library/src/main/java/io/appwrite/services/VectorsDB.kt index 47c4d435..9fa2f291 100644 --- a/library/src/main/java/io/appwrite/services/VectorsDB.kt +++ b/library/src/main/java/io/appwrite/services/VectorsDB.kt @@ -86,6 +86,9 @@ class VectorsDB(client: Client) : Service(client) { suspend fun getTransaction( transactionId: String, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/vectorsdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -122,6 +125,9 @@ class VectorsDB(client: Client) : Service(client) { commit: Boolean? = null, rollback: Boolean? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/vectorsdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -157,6 +163,9 @@ class VectorsDB(client: Client) : Service(client) { suspend fun deleteTransaction( transactionId: String, ): Any { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/vectorsdb/transactions/{transactionId}" .replace("{transactionId}", transactionId) ) @@ -164,6 +173,7 @@ class VectorsDB(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", @@ -186,6 +196,9 @@ class VectorsDB(client: Client) : Service(client) { transactionId: String, operations: List>? = null, ): io.appwrite.models.Transaction { + if (transactionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"transactionId\"") + } val apiPath = ("/vectorsdb/transactions/{transactionId}/operations" .replace("{transactionId}", transactionId) ) @@ -232,6 +245,12 @@ class VectorsDB(client: Client) : Service(client) { ttl: Long? = null, nestedType: Class, ): io.appwrite.models.DocumentList { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -311,6 +330,12 @@ class VectorsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -392,6 +417,12 @@ class VectorsDB(client: Client) : Service(client) { ttl: Long? = null, nestedType: Class, ): io.appwrite.models.DocumentList { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/query" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -471,6 +502,15 @@ class VectorsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -546,6 +586,15 @@ class VectorsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -626,6 +675,15 @@ class VectorsDB(client: Client) : Service(client) { transactionId: String? = null, nestedType: Class, ): io.appwrite.models.Document { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -701,6 +759,15 @@ class VectorsDB(client: Client) : Service(client) { documentId: String, transactionId: String? = null, ): Any { + if (databaseId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"databaseId\"") + } + if (collectionId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"collectionId\"") + } + if (documentId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"documentId\"") + } val apiPath = ("/vectorsdb/{databaseId}/collections/{collectionId}/documents/{documentId}" .replace("{databaseId}", databaseId) .replace("{collectionId}", collectionId) @@ -712,6 +779,7 @@ class VectorsDB(client: Client) : Service(client) { val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", + "accept" to "application/json", ) return client.call( "DELETE", From 3837af7313a34ac67acc000e2a778408ee23eaa1 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 3 Oct 2026 07:57:22 +0100 Subject: [PATCH 10/12] chore: update Android SDK to 29.0.0 --- CHANGELOG.md | 19 + README.md | 63 +- .../account/create-email-password-session.md | 1 + .../java/account/create-id-token-session.md | 2 +- docs/examples/java/account/create-jwt.md | 2 +- docs/examples/java/account/delete-sessions.md | 18 +- docs/examples/java/account/list-sessions.md | 18 +- .../java/apps/create-installation-token.md | 27 + docs/examples/java/apps/create-key.md | 26 + docs/examples/java/apps/create-secret.md | 26 + docs/examples/java/apps/create.md | 44 + .../examples/java/apps/delete-installation.md | 27 + docs/examples/java/apps/delete-key.md | 27 + docs/examples/java/apps/delete-secret.md | 27 + docs/examples/java/apps/delete-tokens.md | 26 + docs/examples/java/apps/delete.md | 26 + docs/examples/java/apps/get-installation.md | 27 + docs/examples/java/apps/get-key.md | 27 + docs/examples/java/apps/get-secret.md | 27 + docs/examples/java/apps/get.md | 26 + .../java/apps/list-installation-scopes.md | 22 + docs/examples/java/apps/list-installations.md | 28 + docs/examples/java/apps/list-keys.md | 28 + .../java/apps/list-o-auth-2-scopes.md | 22 + docs/examples/java/apps/list-secrets.md | 28 + docs/examples/java/apps/list.md | 27 + docs/examples/java/apps/update-team.md | 27 + docs/examples/java/apps/update.md | 45 + docs/examples/java/avatars/delete-photo.md | 22 + docs/examples/java/avatars/get-browser.md | 6 +- docs/examples/java/avatars/get-credit-card.md | 6 +- docs/examples/java/avatars/get-flag.md | 6 +- docs/examples/java/avatars/get-image.md | 4 +- docs/examples/java/avatars/get-initials.md | 4 +- docs/examples/java/avatars/get-photo.md | 6 +- docs/examples/java/avatars/get-qr.md | 4 +- docs/examples/java/avatars/get-screenshot.md | 19 +- docs/examples/java/avatars/update-photo.md | 27 + .../java/databases/create-transaction.md | 2 +- .../databases/decrement-document-attribute.md | 4 +- .../databases/increment-document-attribute.md | 4 +- .../examples/java/databases/list-documents.md | 2 +- .../java/databases/list-transactions.md | 1 + .../java/documentsdb/create-documents.md | 9 +- .../java/documentsdb/create-transaction.md | 2 +- .../decrement-document-attribute.md | 4 +- .../increment-document-attribute.md | 4 +- .../java/documentsdb/list-documents.md | 2 +- .../java/documentsdb/list-transactions.md | 1 + docs/examples/java/graphql/mutation.md | 4 +- docs/examples/java/graphql/query.md | 4 +- docs/examples/java/locale/list-codes.md | 18 +- docs/examples/java/locale/list-continents.md | 18 +- .../examples/java/locale/list-countries-eu.md | 18 +- .../java/locale/list-countries-phones.md | 18 +- docs/examples/java/locale/list-countries.md | 18 +- docs/examples/java/locale/list-currencies.md | 18 +- docs/examples/java/locale/list-languages.md | 18 +- docs/examples/java/oauth2/approve.md | 28 + docs/examples/java/oauth2/authorize-post.md | 39 + docs/examples/java/oauth2/authorize.md | 39 + .../oauth2/create-device-authorization.md | 30 + docs/examples/java/oauth2/create-grant.md | 26 + docs/examples/java/oauth2/create-par.md | 38 + docs/examples/java/oauth2/create-token.md | 35 + docs/examples/java/oauth2/get-grant.md | 26 + .../java/oauth2/list-organizations.md | 28 + docs/examples/java/oauth2/list-projects.md | 28 + docs/examples/java/oauth2/logout-post.md | 31 + docs/examples/java/oauth2/logout.md | 31 + docs/examples/java/oauth2/reject.md | 26 + docs/examples/java/oauth2/revoke.md | 29 + docs/examples/java/presences/list.md | 2 +- .../examples/java/storage/get-file-preview.md | 14 +- .../java/tablesdb/create-transaction.md | 2 +- .../java/tablesdb/decrement-row-column.md | 4 +- .../java/tablesdb/increment-row-column.md | 4 +- docs/examples/java/tablesdb/list-rows.md | 2 +- .../java/tablesdb/list-transactions.md | 1 + docs/examples/java/teams/create-membership.md | 2 +- docs/examples/java/teams/update-membership.md | 2 +- .../java/vectorsdb/create-operations.md | 5 +- docs/examples/java/vectorsdb/create-query.md | 2 +- .../java/vectorsdb/create-transaction.md | 2 +- .../examples/java/vectorsdb/list-documents.md | 2 +- .../java/vectorsdb/list-transactions.md | 1 + .../account/create-email-password-session.md | 1 + .../kotlin/account/delete-sessions.md | 4 +- docs/examples/kotlin/account/list-sessions.md | 4 +- .../kotlin/apps/create-installation-token.md | 16 + docs/examples/kotlin/apps/create-key.md | 15 + docs/examples/kotlin/apps/create-secret.md | 15 + docs/examples/kotlin/apps/create.md | 33 + .../kotlin/apps/delete-installation.md | 16 + docs/examples/kotlin/apps/delete-key.md | 16 + docs/examples/kotlin/apps/delete-secret.md | 16 + docs/examples/kotlin/apps/delete-tokens.md | 15 + docs/examples/kotlin/apps/delete.md | 15 + docs/examples/kotlin/apps/get-installation.md | 16 + docs/examples/kotlin/apps/get-key.md | 16 + docs/examples/kotlin/apps/get-secret.md | 16 + docs/examples/kotlin/apps/get.md | 15 + .../kotlin/apps/list-installation-scopes.md | 13 + .../kotlin/apps/list-installations.md | 17 + docs/examples/kotlin/apps/list-keys.md | 17 + .../kotlin/apps/list-o-auth-2-scopes.md | 13 + docs/examples/kotlin/apps/list-secrets.md | 17 + docs/examples/kotlin/apps/list.md | 16 + docs/examples/kotlin/apps/update-team.md | 16 + docs/examples/kotlin/apps/update.md | 34 + docs/examples/kotlin/avatars/delete-photo.md | 13 + .../examples/kotlin/avatars/get-screenshot.md | 7 +- docs/examples/kotlin/avatars/update-photo.md | 16 + .../databases/decrement-document-attribute.md | 4 +- .../databases/increment-document-attribute.md | 4 +- .../kotlin/databases/list-transactions.md | 1 + .../kotlin/documentsdb/create-documents.md | 9 +- .../decrement-document-attribute.md | 4 +- .../increment-document-attribute.md | 4 +- .../kotlin/documentsdb/list-transactions.md | 1 + docs/examples/kotlin/graphql/mutation.md | 4 +- docs/examples/kotlin/graphql/query.md | 4 +- docs/examples/kotlin/locale/list-codes.md | 4 +- .../examples/kotlin/locale/list-continents.md | 4 +- .../kotlin/locale/list-countries-eu.md | 4 +- .../kotlin/locale/list-countries-phones.md | 4 +- docs/examples/kotlin/locale/list-countries.md | 4 +- .../examples/kotlin/locale/list-currencies.md | 4 +- docs/examples/kotlin/locale/list-languages.md | 4 +- docs/examples/kotlin/oauth2/approve.md | 17 + docs/examples/kotlin/oauth2/authorize-post.md | 28 + docs/examples/kotlin/oauth2/authorize.md | 28 + .../oauth2/create-device-authorization.md | 19 + docs/examples/kotlin/oauth2/create-grant.md | 15 + docs/examples/kotlin/oauth2/create-par.md | 27 + docs/examples/kotlin/oauth2/create-token.md | 24 + docs/examples/kotlin/oauth2/get-grant.md | 15 + .../kotlin/oauth2/list-organizations.md | 17 + docs/examples/kotlin/oauth2/list-projects.md | 17 + docs/examples/kotlin/oauth2/logout-post.md | 20 + docs/examples/kotlin/oauth2/logout.md | 20 + docs/examples/kotlin/oauth2/reject.md | 15 + docs/examples/kotlin/oauth2/revoke.md | 18 + .../kotlin/storage/get-file-preview.md | 2 +- .../kotlin/tablesdb/decrement-row-column.md | 4 +- .../kotlin/tablesdb/increment-row-column.md | 4 +- .../kotlin/tablesdb/list-transactions.md | 1 + .../kotlin/teams/create-membership.md | 2 +- .../kotlin/teams/update-membership.md | 2 +- .../kotlin/vectorsdb/create-operations.md | 5 +- .../kotlin/vectorsdb/list-transactions.md | 1 + example/build.gradle.kts | 10 + library/build.gradle.kts | 5 +- library/consumer-rules.pro | 26 + library/src/main/AndroidManifest.xml | 36 +- library/src/main/java/io/appwrite/Client.kt | 61 +- library/src/main/java/io/appwrite/ID.kt | 4 +- library/src/main/java/io/appwrite/Operator.kt | 7 + library/src/main/java/io/appwrite/Query.kt | 14 +- library/src/main/java/io/appwrite/Topic.kt | 68 ++ .../io/appwrite/cookies/InternalCookie.kt | 32 + .../stores/SharedPreferencesCookieStore.kt | 12 +- .../java/io/appwrite/enums/OAuthProvider.kt | 2 + .../main/java/io/appwrite/models/Account.kt | 266 +++++ .../src/main/java/io/appwrite/models/App.kt | 229 ++++ .../main/java/io/appwrite/models/AppKey.kt | 93 ++ .../java/io/appwrite/models/AppKeyList.kt | 37 + .../main/java/io/appwrite/models/AppScope.kt | 61 + .../java/io/appwrite/models/AppScopeList.kt | 37 + .../main/java/io/appwrite/models/AppSecret.kt | 93 ++ .../java/io/appwrite/models/AppSecretList.kt | 37 + .../io/appwrite/models/AppSecretPlaintext.kt | 93 ++ .../main/java/io/appwrite/models/AppsList.kt | 37 + .../main/java/io/appwrite/models/InputFile.kt | 6 +- .../java/io/appwrite/models/Oauth2Approve.kt | 29 + .../io/appwrite/models/Oauth2Authorize.kt | 37 + .../models/Oauth2DeviceAuthorization.kt | 69 ++ .../java/io/appwrite/models/Oauth2Grant.kt | 117 ++ .../io/appwrite/models/Oauth2Organization.kt | 29 + .../appwrite/models/Oauth2OrganizationList.kt | 37 + .../main/java/io/appwrite/models/Oauth2PAR.kt | 37 + .../java/io/appwrite/models/Oauth2Project.kt | 45 + .../io/appwrite/models/Oauth2ProjectList.kt | 37 + .../java/io/appwrite/models/Oauth2Reject.kt | 29 + .../java/io/appwrite/models/Oauth2Token.kt | 77 ++ .../java/io/appwrite/models/RealtimeModels.kt | 23 +- .../main/java/io/appwrite/services/Account.kt | 35 +- .../main/java/io/appwrite/services/Apps.kt | 881 ++++++++++++++ .../main/java/io/appwrite/services/Avatars.kt | 83 +- .../java/io/appwrite/services/Databases.kt | 6 +- .../java/io/appwrite/services/DocumentsDB.kt | 6 +- .../java/io/appwrite/services/Functions.kt | 3 +- .../main/java/io/appwrite/services/Graphql.kt | 3 +- .../main/java/io/appwrite/services/Locale.kt | 73 +- .../java/io/appwrite/services/Messaging.kt | 3 +- .../main/java/io/appwrite/services/Oauth2.kt | 680 +++++++++++ .../java/io/appwrite/services/Organization.kt | 3 +- .../java/io/appwrite/services/Presences.kt | 3 +- .../main/java/io/appwrite/services/Push.kt | 763 +++++++++++++ .../io/appwrite/services/PushBackground.kt | 1009 +++++++++++++++++ .../java/io/appwrite/services/PushCore.kt | 374 ++++++ .../java/io/appwrite/services/PushReceiver.kt | 33 + .../java/io/appwrite/services/PushService.kt | 67 ++ .../java/io/appwrite/services/PushWakeups.kt | 46 + .../java/io/appwrite/services/Realtime.kt | 16 +- .../main/java/io/appwrite/services/Storage.kt | 9 +- .../java/io/appwrite/services/TablesDB.kt | 6 +- .../main/java/io/appwrite/services/Teams.kt | 7 +- .../java/io/appwrite/services/VectorsDB.kt | 6 +- .../io/appwrite/ClientJavaInteropTest.java | 9 + .../src/test/java/io/appwrite/TopicTest.kt | 53 + 211 files changed, 8007 insertions(+), 239 deletions(-) create mode 100644 docs/examples/java/apps/create-installation-token.md create mode 100644 docs/examples/java/apps/create-key.md create mode 100644 docs/examples/java/apps/create-secret.md create mode 100644 docs/examples/java/apps/create.md create mode 100644 docs/examples/java/apps/delete-installation.md create mode 100644 docs/examples/java/apps/delete-key.md create mode 100644 docs/examples/java/apps/delete-secret.md create mode 100644 docs/examples/java/apps/delete-tokens.md create mode 100644 docs/examples/java/apps/delete.md create mode 100644 docs/examples/java/apps/get-installation.md create mode 100644 docs/examples/java/apps/get-key.md create mode 100644 docs/examples/java/apps/get-secret.md create mode 100644 docs/examples/java/apps/get.md create mode 100644 docs/examples/java/apps/list-installation-scopes.md create mode 100644 docs/examples/java/apps/list-installations.md create mode 100644 docs/examples/java/apps/list-keys.md create mode 100644 docs/examples/java/apps/list-o-auth-2-scopes.md create mode 100644 docs/examples/java/apps/list-secrets.md create mode 100644 docs/examples/java/apps/list.md create mode 100644 docs/examples/java/apps/update-team.md create mode 100644 docs/examples/java/apps/update.md create mode 100644 docs/examples/java/avatars/delete-photo.md create mode 100644 docs/examples/java/avatars/update-photo.md create mode 100644 docs/examples/java/oauth2/approve.md create mode 100644 docs/examples/java/oauth2/authorize-post.md create mode 100644 docs/examples/java/oauth2/authorize.md create mode 100644 docs/examples/java/oauth2/create-device-authorization.md create mode 100644 docs/examples/java/oauth2/create-grant.md create mode 100644 docs/examples/java/oauth2/create-par.md create mode 100644 docs/examples/java/oauth2/create-token.md create mode 100644 docs/examples/java/oauth2/get-grant.md create mode 100644 docs/examples/java/oauth2/list-organizations.md create mode 100644 docs/examples/java/oauth2/list-projects.md create mode 100644 docs/examples/java/oauth2/logout-post.md create mode 100644 docs/examples/java/oauth2/logout.md create mode 100644 docs/examples/java/oauth2/reject.md create mode 100644 docs/examples/java/oauth2/revoke.md create mode 100644 docs/examples/kotlin/apps/create-installation-token.md create mode 100644 docs/examples/kotlin/apps/create-key.md create mode 100644 docs/examples/kotlin/apps/create-secret.md create mode 100644 docs/examples/kotlin/apps/create.md create mode 100644 docs/examples/kotlin/apps/delete-installation.md create mode 100644 docs/examples/kotlin/apps/delete-key.md create mode 100644 docs/examples/kotlin/apps/delete-secret.md create mode 100644 docs/examples/kotlin/apps/delete-tokens.md create mode 100644 docs/examples/kotlin/apps/delete.md create mode 100644 docs/examples/kotlin/apps/get-installation.md create mode 100644 docs/examples/kotlin/apps/get-key.md create mode 100644 docs/examples/kotlin/apps/get-secret.md create mode 100644 docs/examples/kotlin/apps/get.md create mode 100644 docs/examples/kotlin/apps/list-installation-scopes.md create mode 100644 docs/examples/kotlin/apps/list-installations.md create mode 100644 docs/examples/kotlin/apps/list-keys.md create mode 100644 docs/examples/kotlin/apps/list-o-auth-2-scopes.md create mode 100644 docs/examples/kotlin/apps/list-secrets.md create mode 100644 docs/examples/kotlin/apps/list.md create mode 100644 docs/examples/kotlin/apps/update-team.md create mode 100644 docs/examples/kotlin/apps/update.md create mode 100644 docs/examples/kotlin/avatars/delete-photo.md create mode 100644 docs/examples/kotlin/avatars/update-photo.md create mode 100644 docs/examples/kotlin/oauth2/approve.md create mode 100644 docs/examples/kotlin/oauth2/authorize-post.md create mode 100644 docs/examples/kotlin/oauth2/authorize.md create mode 100644 docs/examples/kotlin/oauth2/create-device-authorization.md create mode 100644 docs/examples/kotlin/oauth2/create-grant.md create mode 100644 docs/examples/kotlin/oauth2/create-par.md create mode 100644 docs/examples/kotlin/oauth2/create-token.md create mode 100644 docs/examples/kotlin/oauth2/get-grant.md create mode 100644 docs/examples/kotlin/oauth2/list-organizations.md create mode 100644 docs/examples/kotlin/oauth2/list-projects.md create mode 100644 docs/examples/kotlin/oauth2/logout-post.md create mode 100644 docs/examples/kotlin/oauth2/logout.md create mode 100644 docs/examples/kotlin/oauth2/reject.md create mode 100644 docs/examples/kotlin/oauth2/revoke.md create mode 100644 library/consumer-rules.pro create mode 100644 library/src/main/java/io/appwrite/Topic.kt create mode 100644 library/src/main/java/io/appwrite/models/Account.kt create mode 100644 library/src/main/java/io/appwrite/models/App.kt create mode 100644 library/src/main/java/io/appwrite/models/AppKey.kt create mode 100644 library/src/main/java/io/appwrite/models/AppKeyList.kt create mode 100644 library/src/main/java/io/appwrite/models/AppScope.kt create mode 100644 library/src/main/java/io/appwrite/models/AppScopeList.kt create mode 100644 library/src/main/java/io/appwrite/models/AppSecret.kt create mode 100644 library/src/main/java/io/appwrite/models/AppSecretList.kt create mode 100644 library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt create mode 100644 library/src/main/java/io/appwrite/models/AppsList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Approve.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Authorize.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Grant.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Organization.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2PAR.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Project.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Reject.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Token.kt create mode 100644 library/src/main/java/io/appwrite/services/Apps.kt create mode 100644 library/src/main/java/io/appwrite/services/Oauth2.kt create mode 100644 library/src/main/java/io/appwrite/services/Push.kt create mode 100644 library/src/main/java/io/appwrite/services/PushBackground.kt create mode 100644 library/src/main/java/io/appwrite/services/PushCore.kt create mode 100644 library/src/main/java/io/appwrite/services/PushReceiver.kt create mode 100644 library/src/main/java/io/appwrite/services/PushService.kt create mode 100644 library/src/main/java/io/appwrite/services/PushWakeups.kt create mode 100644 library/src/test/java/io/appwrite/TopicTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 435676ce..3299fb95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Change Log +## 29.0.0 + +* Breaking: minimum Android SDK raised from 23 to 24 (Android 7.0) +* Added: `Push` service for MQTT push messages without FCM, plus `PushReceiver` for background handling +* Added: `Topic` builder for push topics +* Added: `endpointPush` and `pushClientId` on `Client`, with `setPushEndpoint` and `setPushClientId` +* Added: `Apps` service for managing OAuth2 apps, keys, secrets and installations +* Added: `Oauth2` service for using Appwrite as an OAuth2/OIDC provider +* Added: `Avatars.updatePhoto` and `Avatars.deletePhoto` +* Added: `duration` parameter to `Account.createEmailPasswordSession` +* Added: `current` parameter to `Account.deleteSessions` +* Added: `total` parameter to `Account.listSessions`, `Locale` list methods and `listTransactions` +* Added: `WEBFLOW` value to `OAuthProvider` +* Added: `toMap()` and `toJson()` on `Query` and `Operator` +* Updated: SDK manifest merges Push permissions, a foreground service and a boot receiver into apps +* Updated: cookie store, realtime and query parsing no longer rely on Gson reflection, so they work under R8 +* Updated: added `hivemq-mqtt-client` dependency and consumer ProGuard rules +* Fixed: `ID.unique()` and `InputFile.fromFile` no longer crash on Android 7.x + ## 28.0.0 * Breaking: removed `Account.listLogs` and the `Log`, `LogList` models diff --git a/README.md b/README.md index c8fb25f0..524161da 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:28.0.0") +implementation("io.appwrite:sdk-for-android:29.0.0") ``` ### Maven @@ -49,11 +49,70 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 28.0.0 + 29.0.0 ``` +### Code shrinking (R8) + +The SDK ships the R8 rules it needs, so no extra configuration is required. Your own classes +that the SDK fills from JSON (the `nestedType` of documents, rows and preferences, and the +`payloadType` of Realtime subscriptions) are read by Gson through reflection. Annotate them +with `@Keep` so R8 keeps their fields: + +```kotlin +@Keep +data class Note(val title: String, val body: String) +``` + +### Push notifications + +The `Push` service pulls in HiveMQ (which bundles Netty). Netty's jars ship duplicate +`META-INF` metadata that Android's packager rejects with a "More than one file was found +with OS independent path" error. If you use `Push`, add these excludes to the `android` block +of your **application** module's `build.gradle(.kts)` (packaging is resolved when the app is +assembled, so a library dependency cannot apply this for you): + +```kotlin +android { + packaging { + resources { + excludes += "/META-INF/INDEX.LIST" + excludes += "/META-INF/io.netty.versions.properties" + excludes += "/META-INF/DEPENDENCIES" + } + } +} +``` + +#### Background delivery + +A subscription with `background = true` keeps delivering after the app is backgrounded, its +process is killed, or the device restarts, until it is unsubscribed or `push.close()` is +called (do this on sign-out). No service is kept running: a scheduled job and alarm wake the +app every 15 to 60 seconds to reconnect, and the broker replays what was sent in between +(`retry = true`). It reconnects with the credential saved at subscribe time, so use a session +(`client.setSession(...)`), not a short-lived JWT. + +When no in-app callback is listening, each message is posted as a notification that opens +your launch activity with `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` in its extras. Set its +icon with `` +in your ``. To handle these messages in code, subclass `PushReceiver` and declare +it with the `io.appwrite.push.MESSAGE` action (see its docs). + +Doze delays the scheduled runs. They are more punctual when your app may schedule exact +alarms (`SCHEDULE_EXACT_ALARM`) or is exempt from battery optimisation; both are your app's +choice and subject to Google Play policy, and the SDK uses them when granted. For immediate delivery even after a kill and +during Doze, call `push.setForeground(true)` from the foreground: it adds a foreground service +with a quiet ongoing notification. If you never enable it, you can remove the service from +your merged manifest, so the Play Console does not ask you to declare it: + +```xml + + +``` + ## Getting Started diff --git a/docs/examples/java/account/create-email-password-session.md b/docs/examples/java/account/create-email-password-session.md index fa7d2523..9e254bd2 100644 --- a/docs/examples/java/account/create-email-password-session.md +++ b/docs/examples/java/account/create-email-password-session.md @@ -14,6 +14,7 @@ Account account = new Account(client); account.createEmailPasswordSession( "email@example.com", // email "password", // password + 60L, // duration (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/account/create-id-token-session.md b/docs/examples/java/account/create-id-token-session.md index 457b9fee..b7bd83fc 100644 --- a/docs/examples/java/account/create-id-token-session.md +++ b/docs/examples/java/account/create-id-token-session.md @@ -17,7 +17,7 @@ account.createIdTokenSession( "", // idToken "", // nonce (optional) "", // accessToken (optional) - 0, // accessTokenExpiry (optional) + 0L, // accessTokenExpiry (optional) "", // name (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/account/create-jwt.md b/docs/examples/java/account/create-jwt.md index f405d306..c41c5721 100644 --- a/docs/examples/java/account/create-jwt.md +++ b/docs/examples/java/account/create-jwt.md @@ -12,7 +12,7 @@ Client client = new Client(context) Account account = new Account(client); account.createJWT( - 0, // duration (optional) + 0L, // duration (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/account/delete-sessions.md b/docs/examples/java/account/delete-sessions.md index 4b93d19a..e0dce6ca 100644 --- a/docs/examples/java/account/delete-sessions.md +++ b/docs/examples/java/account/delete-sessions.md @@ -11,12 +11,16 @@ Client client = new Client(context) Account account = new Account(client); -account.deleteSessions(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +account.deleteSessions( + false, // current (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/account/list-sessions.md b/docs/examples/java/account/list-sessions.md index 9e52ea47..5ea50625 100644 --- a/docs/examples/java/account/list-sessions.md +++ b/docs/examples/java/account/list-sessions.md @@ -11,12 +11,16 @@ Client client = new Client(context) Account account = new Account(client); -account.listSessions(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +account.listSessions( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/apps/create-installation-token.md b/docs/examples/java/apps/create-installation-token.md new file mode 100644 index 00000000..e46fc2af --- /dev/null +++ b/docs/examples/java/apps/create-installation-token.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.createInstallationToken( + "", // appId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/create-key.md b/docs/examples/java/apps/create-key.md new file mode 100644 index 00000000..729c98fd --- /dev/null +++ b/docs/examples/java/apps/create-key.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.createKey( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/create-secret.md b/docs/examples/java/apps/create-secret.md new file mode 100644 index 00000000..8acb44ef --- /dev/null +++ b/docs/examples/java/apps/create-secret.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.createSecret( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/create.md b/docs/examples/java/apps/create.md new file mode 100644 index 00000000..88694a94 --- /dev/null +++ b/docs/examples/java/apps/create.md @@ -0,0 +1,44 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.create( + "", // appId + "", // name + List.of(), // redirectUris + "", // description (optional) + "https://example.com", // clientUri (optional) + "https://example.com", // logoUri (optional) + "https://example.com", // privacyPolicyUrl (optional) + "https://example.com", // termsUrl (optional) + List.of(), // contacts (optional) + "", // tagline (optional) + List.of(), // tags (optional) + List.of(), // images (optional) + "https://example.com", // supportUrl (optional) + "https://example.com", // dataDeletionUrl (optional) + List.of(), // postLogoutRedirectUris (optional) + false, // enabled (optional) + "public", // type (optional) + false, // deviceFlow (optional) + "", // teamId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-installation.md b/docs/examples/java/apps/delete-installation.md new file mode 100644 index 00000000..559ab654 --- /dev/null +++ b/docs/examples/java/apps/delete-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteInstallation( + "", // appId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-key.md b/docs/examples/java/apps/delete-key.md new file mode 100644 index 00000000..312e0be0 --- /dev/null +++ b/docs/examples/java/apps/delete-key.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteKey( + "", // appId + "", // keyId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-secret.md b/docs/examples/java/apps/delete-secret.md new file mode 100644 index 00000000..b21fc759 --- /dev/null +++ b/docs/examples/java/apps/delete-secret.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteSecret( + "", // appId + "", // secretId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-tokens.md b/docs/examples/java/apps/delete-tokens.md new file mode 100644 index 00000000..f873873e --- /dev/null +++ b/docs/examples/java/apps/delete-tokens.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteTokens( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete.md b/docs/examples/java/apps/delete.md new file mode 100644 index 00000000..0c99520c --- /dev/null +++ b/docs/examples/java/apps/delete.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.delete( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get-installation.md b/docs/examples/java/apps/get-installation.md new file mode 100644 index 00000000..f8cf6b56 --- /dev/null +++ b/docs/examples/java/apps/get-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.getInstallation( + "", // appId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get-key.md b/docs/examples/java/apps/get-key.md new file mode 100644 index 00000000..729ccfa6 --- /dev/null +++ b/docs/examples/java/apps/get-key.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.getKey( + "", // appId + "", // keyId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get-secret.md b/docs/examples/java/apps/get-secret.md new file mode 100644 index 00000000..f4f3c68f --- /dev/null +++ b/docs/examples/java/apps/get-secret.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.getSecret( + "", // appId + "", // secretId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get.md b/docs/examples/java/apps/get.md new file mode 100644 index 00000000..0b4c5378 --- /dev/null +++ b/docs/examples/java/apps/get.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.get( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list-installation-scopes.md b/docs/examples/java/apps/list-installation-scopes.md new file mode 100644 index 00000000..c09da789 --- /dev/null +++ b/docs/examples/java/apps/list-installation-scopes.md @@ -0,0 +1,22 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listInstallationScopes(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); +})); +``` diff --git a/docs/examples/java/apps/list-installations.md b/docs/examples/java/apps/list-installations.md new file mode 100644 index 00000000..f83097e1 --- /dev/null +++ b/docs/examples/java/apps/list-installations.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listInstallations( + "", // appId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list-keys.md b/docs/examples/java/apps/list-keys.md new file mode 100644 index 00000000..7a872751 --- /dev/null +++ b/docs/examples/java/apps/list-keys.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listKeys( + "", // appId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list-o-auth-2-scopes.md b/docs/examples/java/apps/list-o-auth-2-scopes.md new file mode 100644 index 00000000..d96f8c36 --- /dev/null +++ b/docs/examples/java/apps/list-o-auth-2-scopes.md @@ -0,0 +1,22 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listOAuth2Scopes(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); +})); +``` diff --git a/docs/examples/java/apps/list-secrets.md b/docs/examples/java/apps/list-secrets.md new file mode 100644 index 00000000..4d823a74 --- /dev/null +++ b/docs/examples/java/apps/list-secrets.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listSecrets( + "", // appId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list.md b/docs/examples/java/apps/list.md new file mode 100644 index 00000000..31d80daa --- /dev/null +++ b/docs/examples/java/apps/list.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.list( + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/update-team.md b/docs/examples/java/apps/update-team.md new file mode 100644 index 00000000..314c672f --- /dev/null +++ b/docs/examples/java/apps/update-team.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.updateTeam( + "", // appId + "", // teamId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/update.md b/docs/examples/java/apps/update.md new file mode 100644 index 00000000..e8b51fa9 --- /dev/null +++ b/docs/examples/java/apps/update.md @@ -0,0 +1,45 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.update( + "", // appId + "", // name + "", // description (optional) + "https://example.com", // clientUri (optional) + "https://example.com", // logoUri (optional) + "https://example.com", // privacyPolicyUrl (optional) + "https://example.com", // termsUrl (optional) + List.of(), // contacts (optional) + "", // tagline (optional) + List.of(), // tags (optional) + List.of(), // images (optional) + "https://example.com", // supportUrl (optional) + "https://example.com", // dataDeletionUrl (optional) + false, // enabled (optional) + List.of(), // redirectUris (optional) + List.of(), // postLogoutRedirectUris (optional) + "public", // type (optional) + false, // deviceFlow (optional) + List.of(), // installationScopes (optional) + "https://example.com", // installationRedirectUrl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/avatars/delete-photo.md b/docs/examples/java/avatars/delete-photo.md new file mode 100644 index 00000000..cffc010c --- /dev/null +++ b/docs/examples/java/avatars/delete-photo.md @@ -0,0 +1,22 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Avatars; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Avatars avatars = new Avatars(client); + +avatars.deletePhoto(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); +})); +``` diff --git a/docs/examples/java/avatars/get-browser.md b/docs/examples/java/avatars/get-browser.md index f101ad54..626de034 100644 --- a/docs/examples/java/avatars/get-browser.md +++ b/docs/examples/java/avatars/get-browser.md @@ -14,9 +14,9 @@ Avatars avatars = new Avatars(client); avatars.getBrowser( Browser.AVANT_BROWSER, // code - 0, // width (optional) - 0, // height (optional) - -1, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + -1L, // quality (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-credit-card.md b/docs/examples/java/avatars/get-credit-card.md index b12cebc9..f8e114db 100644 --- a/docs/examples/java/avatars/get-credit-card.md +++ b/docs/examples/java/avatars/get-credit-card.md @@ -14,9 +14,9 @@ Avatars avatars = new Avatars(client); avatars.getCreditCard( CreditCard.AMERICAN_EXPRESS, // code - 0, // width (optional) - 0, // height (optional) - -1, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + -1L, // quality (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-flag.md b/docs/examples/java/avatars/get-flag.md index 4d808513..0acd391f 100644 --- a/docs/examples/java/avatars/get-flag.md +++ b/docs/examples/java/avatars/get-flag.md @@ -14,9 +14,9 @@ Avatars avatars = new Avatars(client); avatars.getFlag( Flag.AFGHANISTAN, // code - 0, // width (optional) - 0, // height (optional) - -1, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + -1L, // quality (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-image.md b/docs/examples/java/avatars/get-image.md index a22041f5..38bd4dc8 100644 --- a/docs/examples/java/avatars/get-image.md +++ b/docs/examples/java/avatars/get-image.md @@ -13,8 +13,8 @@ Avatars avatars = new Avatars(client); avatars.getImage( "https://example.com", // url - 0, // width (optional) - 0, // height (optional) + 0L, // width (optional) + 0L, // height (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-initials.md b/docs/examples/java/avatars/get-initials.md index 08f5bea9..c22358d2 100644 --- a/docs/examples/java/avatars/get-initials.md +++ b/docs/examples/java/avatars/get-initials.md @@ -13,8 +13,8 @@ Avatars avatars = new Avatars(client); avatars.getInitials( "", // name (optional) - 0, // width (optional) - 0, // height (optional) + 0L, // width (optional) + 0L, // height (optional) "FFFFFF", // background (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/avatars/get-photo.md b/docs/examples/java/avatars/get-photo.md index 2204db78..04769d1c 100644 --- a/docs/examples/java/avatars/get-photo.md +++ b/docs/examples/java/avatars/get-photo.md @@ -12,9 +12,9 @@ Client client = new Client(context) Avatars avatars = new Avatars(client); avatars.getPhoto( - 0, // width (optional) - 0, // height (optional) - 0, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + 0L, // quality (optional) "png", // output (optional) "g", // rating (optional) "current()", // userId (optional) diff --git a/docs/examples/java/avatars/get-qr.md b/docs/examples/java/avatars/get-qr.md index ad08ab56..b3bbb6b6 100644 --- a/docs/examples/java/avatars/get-qr.md +++ b/docs/examples/java/avatars/get-qr.md @@ -13,8 +13,8 @@ Avatars avatars = new Avatars(client); avatars.getQR( "", // text - 1, // size (optional) - 0, // margin (optional) + 1L, // size (optional) + 0L, // margin (optional) false, // download (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/avatars/get-screenshot.md b/docs/examples/java/avatars/get-screenshot.md index 71f96c5b..82454a65 100644 --- a/docs/examples/java/avatars/get-screenshot.md +++ b/docs/examples/java/avatars/get-screenshot.md @@ -18,12 +18,11 @@ Avatars avatars = new Avatars(client); avatars.getScreenshot( "https://example.com", // url Map.of( - "Authorization", "Bearer token123", - "X-Custom-Header", "value" + "Accept-Language", "en-US,en;q=0.9" ), // headers (optional) - 1920, // viewportWidth (optional) - 1080, // viewportHeight (optional) - 2, // scale (optional) + 1920L, // viewportWidth (optional) + 1080L, // viewportHeight (optional) + 2.0, // scale (optional) BrowserTheme.LIGHT, // theme (optional) "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15", // userAgent (optional) true, // fullpage (optional) @@ -31,13 +30,13 @@ avatars.getScreenshot( Timezone.AFRICA_ABIDJAN, // timezone (optional) 37.7749, // latitude (optional) -122.4194, // longitude (optional) - 100, // accuracy (optional) + 100.0, // accuracy (optional) true, // touch (optional) BrowserPermission.GEOLOCATION, // permissions (optional) - 3, // sleep (optional) - 800, // width (optional) - 600, // height (optional) - 85, // quality (optional) + 3L, // sleep (optional) + 800L, // width (optional) + 600L, // height (optional) + 85L, // quality (optional) ImageFormat.JPG, // output (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/avatars/update-photo.md b/docs/examples/java/avatars/update-photo.md new file mode 100644 index 00000000..8c4d6e90 --- /dev/null +++ b/docs/examples/java/avatars/update-photo.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.models.InputFile; +import io.appwrite.services.Avatars; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Avatars avatars = new Avatars(client); + +avatars.updatePhoto( + InputFile.fromPath("file.png"), // file + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/databases/create-transaction.md b/docs/examples/java/databases/create-transaction.md index d96c27a0..616838d2 100644 --- a/docs/examples/java/databases/create-transaction.md +++ b/docs/examples/java/databases/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) Databases databases = new Databases(client); databases.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/databases/decrement-document-attribute.md b/docs/examples/java/databases/decrement-document-attribute.md index 1a2f23e2..682dadd2 100644 --- a/docs/examples/java/databases/decrement-document-attribute.md +++ b/docs/examples/java/databases/decrement-document-attribute.md @@ -16,8 +16,8 @@ databases.decrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 0, // min (optional) + 1.0, // value (optional) + 0.0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/databases/increment-document-attribute.md b/docs/examples/java/databases/increment-document-attribute.md index fe363592..07dfd55c 100644 --- a/docs/examples/java/databases/increment-document-attribute.md +++ b/docs/examples/java/databases/increment-document-attribute.md @@ -16,8 +16,8 @@ databases.incrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 100, // max (optional) + 1.0, // value (optional) + 100.0, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/databases/list-documents.md b/docs/examples/java/databases/list-documents.md index 8569cb60..8a5a52cb 100644 --- a/docs/examples/java/databases/list-documents.md +++ b/docs/examples/java/databases/list-documents.md @@ -17,7 +17,7 @@ databases.listDocuments( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/databases/list-transactions.md b/docs/examples/java/databases/list-transactions.md index ad11c7f7..3e7ca138 100644 --- a/docs/examples/java/databases/list-transactions.md +++ b/docs/examples/java/databases/list-transactions.md @@ -13,6 +13,7 @@ Databases databases = new Databases(client); databases.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/documentsdb/create-documents.md b/docs/examples/java/documentsdb/create-documents.md index c52433a2..ae20335d 100644 --- a/docs/examples/java/documentsdb/create-documents.md +++ b/docs/examples/java/documentsdb/create-documents.md @@ -14,7 +14,14 @@ DocumentsDB documentsDB = new DocumentsDB(client); documentsDB.createDocuments( "", // databaseId "", // collectionId - List.of(), // documents + List.of(Map.of( + "$id", "example1", + "username", "walter.obrien", + "email", "walter.obrien@example.com", + "fullName", "Walter O'Brien", + "age", 30, + "isAdmin", false + )), // documents "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/documentsdb/create-transaction.md b/docs/examples/java/documentsdb/create-transaction.md index da91c5c8..ac4a96d8 100644 --- a/docs/examples/java/documentsdb/create-transaction.md +++ b/docs/examples/java/documentsdb/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) DocumentsDB documentsDB = new DocumentsDB(client); documentsDB.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/documentsdb/decrement-document-attribute.md b/docs/examples/java/documentsdb/decrement-document-attribute.md index f1853ac6..9e19095e 100644 --- a/docs/examples/java/documentsdb/decrement-document-attribute.md +++ b/docs/examples/java/documentsdb/decrement-document-attribute.md @@ -16,8 +16,8 @@ documentsDB.decrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 0, // min (optional) + 1.0, // value (optional) + 0.0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/documentsdb/increment-document-attribute.md b/docs/examples/java/documentsdb/increment-document-attribute.md index fab214e9..470abbf8 100644 --- a/docs/examples/java/documentsdb/increment-document-attribute.md +++ b/docs/examples/java/documentsdb/increment-document-attribute.md @@ -16,8 +16,8 @@ documentsDB.incrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 100, // max (optional) + 1.0, // value (optional) + 100.0, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/documentsdb/list-documents.md b/docs/examples/java/documentsdb/list-documents.md index 0abb6734..f6cfe483 100644 --- a/docs/examples/java/documentsdb/list-documents.md +++ b/docs/examples/java/documentsdb/list-documents.md @@ -17,7 +17,7 @@ documentsDB.listDocuments( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/documentsdb/list-transactions.md b/docs/examples/java/documentsdb/list-transactions.md index 9b8fcd7b..ab4974a0 100644 --- a/docs/examples/java/documentsdb/list-transactions.md +++ b/docs/examples/java/documentsdb/list-transactions.md @@ -13,6 +13,7 @@ DocumentsDB documentsDB = new DocumentsDB(client); documentsDB.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/graphql/mutation.md b/docs/examples/java/graphql/mutation.md index 9cc7febe..e422d6f1 100644 --- a/docs/examples/java/graphql/mutation.md +++ b/docs/examples/java/graphql/mutation.md @@ -12,7 +12,9 @@ Client client = new Client(context) Graphql graphql = new Graphql(client); graphql.mutation( - Map.of("a", "b"), // query + Map.of( + "query", "mutation { accountUpdateName(name: "Walter") { name } }" + ), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/graphql/query.md b/docs/examples/java/graphql/query.md index 25704b00..58e63635 100644 --- a/docs/examples/java/graphql/query.md +++ b/docs/examples/java/graphql/query.md @@ -12,7 +12,9 @@ Client client = new Client(context) Graphql graphql = new Graphql(client); graphql.query( - Map.of("a", "b"), // query + Map.of( + "query", "query { localeGet { ip } }" + ), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/locale/list-codes.md b/docs/examples/java/locale/list-codes.md index 534b1199..181c1b8f 100644 --- a/docs/examples/java/locale/list-codes.md +++ b/docs/examples/java/locale/list-codes.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCodes(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCodes( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-continents.md b/docs/examples/java/locale/list-continents.md index 998a31be..e8a60fca 100644 --- a/docs/examples/java/locale/list-continents.md +++ b/docs/examples/java/locale/list-continents.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listContinents(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listContinents( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-countries-eu.md b/docs/examples/java/locale/list-countries-eu.md index 5950c990..b6c878fc 100644 --- a/docs/examples/java/locale/list-countries-eu.md +++ b/docs/examples/java/locale/list-countries-eu.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCountriesEU(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCountriesEU( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-countries-phones.md b/docs/examples/java/locale/list-countries-phones.md index 6096758e..3d8bb9eb 100644 --- a/docs/examples/java/locale/list-countries-phones.md +++ b/docs/examples/java/locale/list-countries-phones.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCountriesPhones(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCountriesPhones( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-countries.md b/docs/examples/java/locale/list-countries.md index 140fc48a..3238cf27 100644 --- a/docs/examples/java/locale/list-countries.md +++ b/docs/examples/java/locale/list-countries.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCountries(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCountries( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-currencies.md b/docs/examples/java/locale/list-currencies.md index 0e5273d9..631bb346 100644 --- a/docs/examples/java/locale/list-currencies.md +++ b/docs/examples/java/locale/list-currencies.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCurrencies(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCurrencies( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-languages.md b/docs/examples/java/locale/list-languages.md index e85bd0a1..b0000633 100644 --- a/docs/examples/java/locale/list-languages.md +++ b/docs/examples/java/locale/list-languages.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listLanguages(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listLanguages( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/oauth2/approve.md b/docs/examples/java/oauth2/approve.md new file mode 100644 index 00000000..f2f4a495 --- /dev/null +++ b/docs/examples/java/oauth2/approve.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.approve( + "", // grant_id + "", // authorization_details (optional) + "", // scope (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/authorize-post.md b/docs/examples/java/oauth2/authorize-post.md new file mode 100644 index 00000000..99ed6b8e --- /dev/null +++ b/docs/examples/java/oauth2/authorize-post.md @@ -0,0 +1,39 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.authorizePost( + "", // client_id (optional) + "https://example.com", // redirect_uri (optional) + "", // response_type (optional) + "", // scope (optional) + "", // state (optional) + "", // nonce (optional) + "", // code_challenge (optional) + "s256", // code_challenge_method (optional) + "", // prompt (optional) + 0L, // max_age (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + "", // request_uri (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/authorize.md b/docs/examples/java/oauth2/authorize.md new file mode 100644 index 00000000..62a452c8 --- /dev/null +++ b/docs/examples/java/oauth2/authorize.md @@ -0,0 +1,39 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.authorize( + "", // client_id (optional) + "https://example.com", // redirect_uri (optional) + "", // response_type (optional) + "", // scope (optional) + "", // state (optional) + "", // nonce (optional) + "", // code_challenge (optional) + "s256", // code_challenge_method (optional) + "", // prompt (optional) + 0L, // max_age (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + "", // request_uri (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-device-authorization.md b/docs/examples/java/oauth2/create-device-authorization.md new file mode 100644 index 00000000..0b65a1ab --- /dev/null +++ b/docs/examples/java/oauth2/create-device-authorization.md @@ -0,0 +1,30 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createDeviceAuthorization( + "", // client_id (optional) + "", // scope (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-grant.md b/docs/examples/java/oauth2/create-grant.md new file mode 100644 index 00000000..a14c3886 --- /dev/null +++ b/docs/examples/java/oauth2/create-grant.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createGrant( + "", // user_code + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-par.md b/docs/examples/java/oauth2/create-par.md new file mode 100644 index 00000000..e65bdb54 --- /dev/null +++ b/docs/examples/java/oauth2/create-par.md @@ -0,0 +1,38 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createPAR( + "", // client_id + "https://example.com", // redirect_uri + "code", // response_type + "", // scope (optional) + "", // state (optional) + "", // nonce (optional) + "", // code_challenge (optional) + "s256", // code_challenge_method (optional) + "", // prompt (optional) + 0L, // max_age (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-token.md b/docs/examples/java/oauth2/create-token.md new file mode 100644 index 00000000..39b6fa25 --- /dev/null +++ b/docs/examples/java/oauth2/create-token.md @@ -0,0 +1,35 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createToken( + "", // grant_type + "", // code (optional) + "", // refresh_token (optional) + "", // device_code (optional) + "", // client_id (optional) + "", // client_secret (optional) + "", // code_verifier (optional) + "https://example.com", // redirect_uri (optional) + "", // resource (optional) + "", // audience (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/get-grant.md b/docs/examples/java/oauth2/get-grant.md new file mode 100644 index 00000000..18068889 --- /dev/null +++ b/docs/examples/java/oauth2/get-grant.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.getGrant( + "", // grant_id + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/list-organizations.md b/docs/examples/java/oauth2/list-organizations.md new file mode 100644 index 00000000..bbc74a78 --- /dev/null +++ b/docs/examples/java/oauth2/list-organizations.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.listOrganizations( + 1L, // limit (optional) + 0L, // offset (optional) + "", // search (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/list-projects.md b/docs/examples/java/oauth2/list-projects.md new file mode 100644 index 00000000..6963583a --- /dev/null +++ b/docs/examples/java/oauth2/list-projects.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.listProjects( + 1L, // limit (optional) + 0L, // offset (optional) + "", // search (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/logout-post.md b/docs/examples/java/oauth2/logout-post.md new file mode 100644 index 00000000..fefd5e92 --- /dev/null +++ b/docs/examples/java/oauth2/logout-post.md @@ -0,0 +1,31 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.logoutPost( + "", // id_token_hint (optional) + "", // logout_hint (optional) + "", // client_id (optional) + "https://example.com", // post_logout_redirect_uri (optional) + "", // state (optional) + "", // ui_locales (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/logout.md b/docs/examples/java/oauth2/logout.md new file mode 100644 index 00000000..ca7739a2 --- /dev/null +++ b/docs/examples/java/oauth2/logout.md @@ -0,0 +1,31 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.logout( + "", // id_token_hint (optional) + "", // logout_hint (optional) + "", // client_id (optional) + "https://example.com", // post_logout_redirect_uri (optional) + "", // state (optional) + "", // ui_locales (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/reject.md b/docs/examples/java/oauth2/reject.md new file mode 100644 index 00000000..90b9b2e2 --- /dev/null +++ b/docs/examples/java/oauth2/reject.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.reject( + "", // grant_id + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/revoke.md b/docs/examples/java/oauth2/revoke.md new file mode 100644 index 00000000..0d680eb1 --- /dev/null +++ b/docs/examples/java/oauth2/revoke.md @@ -0,0 +1,29 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.revoke( + "", // token + "access_token", // token_type_hint (optional) + "", // client_id (optional) + "", // client_secret (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/presences/list.md b/docs/examples/java/presences/list.md index d66a4161..c2430748 100644 --- a/docs/examples/java/presences/list.md +++ b/docs/examples/java/presences/list.md @@ -14,7 +14,7 @@ Presences presences = new Presences(client); presences.list( List.of(), // queries (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/storage/get-file-preview.md b/docs/examples/java/storage/get-file-preview.md index 3d753c44..2f1060a1 100644 --- a/docs/examples/java/storage/get-file-preview.md +++ b/docs/examples/java/storage/get-file-preview.md @@ -16,15 +16,15 @@ Storage storage = new Storage(client); storage.getFilePreview( "", // bucketId "", // fileId - 0, // width (optional) - 0, // height (optional) + 0L, // width (optional) + 0L, // height (optional) ImageGravity.AUTO, // gravity (optional) - -1, // quality (optional) - 0, // borderWidth (optional) + -1L, // quality (optional) + 0L, // borderWidth (optional) "FFFFFF", // borderColor (optional) - 0, // borderRadius (optional) - 0, // opacity (optional) - -360, // rotation (optional) + 0L, // borderRadius (optional) + 0.0, // opacity (optional) + -360L, // rotation (optional) "FFFFFF", // background (optional) ImageFormat.JPG, // output (optional) "", // token (optional) diff --git a/docs/examples/java/tablesdb/create-transaction.md b/docs/examples/java/tablesdb/create-transaction.md index 8401bfec..0dab2f0c 100644 --- a/docs/examples/java/tablesdb/create-transaction.md +++ b/docs/examples/java/tablesdb/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) TablesDB tablesDB = new TablesDB(client); tablesDB.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/tablesdb/decrement-row-column.md b/docs/examples/java/tablesdb/decrement-row-column.md index c998b442..2315f165 100644 --- a/docs/examples/java/tablesdb/decrement-row-column.md +++ b/docs/examples/java/tablesdb/decrement-row-column.md @@ -16,8 +16,8 @@ tablesDB.decrementRowColumn( "", // tableId "", // rowId "", // column - 1, // value (optional) - 0, // min (optional) + 1.0, // value (optional) + 0.0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/tablesdb/increment-row-column.md b/docs/examples/java/tablesdb/increment-row-column.md index 5fb7189e..2e9b52ba 100644 --- a/docs/examples/java/tablesdb/increment-row-column.md +++ b/docs/examples/java/tablesdb/increment-row-column.md @@ -16,8 +16,8 @@ tablesDB.incrementRowColumn( "", // tableId "", // rowId "", // column - 1, // value (optional) - 100, // max (optional) + 1.0, // value (optional) + 100.0, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/tablesdb/list-rows.md b/docs/examples/java/tablesdb/list-rows.md index cbf40287..6d3a0a78 100644 --- a/docs/examples/java/tablesdb/list-rows.md +++ b/docs/examples/java/tablesdb/list-rows.md @@ -17,7 +17,7 @@ tablesDB.listRows( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/tablesdb/list-transactions.md b/docs/examples/java/tablesdb/list-transactions.md index 9ebb180a..8a04ad9c 100644 --- a/docs/examples/java/tablesdb/list-transactions.md +++ b/docs/examples/java/tablesdb/list-transactions.md @@ -13,6 +13,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/teams/create-membership.md b/docs/examples/java/teams/create-membership.md index 861aeb62..d7fba7c1 100644 --- a/docs/examples/java/teams/create-membership.md +++ b/docs/examples/java/teams/create-membership.md @@ -13,7 +13,7 @@ Teams teams = new Teams(client); teams.createMembership( "", // teamId - List.of(), // roles + List.of("editor"), // roles "email@example.com", // email (optional) "", // userId (optional) "+12065550100", // phone (optional) diff --git a/docs/examples/java/teams/update-membership.md b/docs/examples/java/teams/update-membership.md index 60231f2c..c4208a83 100644 --- a/docs/examples/java/teams/update-membership.md +++ b/docs/examples/java/teams/update-membership.md @@ -14,7 +14,7 @@ Teams teams = new Teams(client); teams.updateMembership( "", // teamId "", // membershipId - List.of(), // roles + List.of("editor"), // roles new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/create-operations.md b/docs/examples/java/vectorsdb/create-operations.md index 7e503634..8dd6f0b8 100644 --- a/docs/examples/java/vectorsdb/create-operations.md +++ b/docs/examples/java/vectorsdb/create-operations.md @@ -19,7 +19,10 @@ vectorsDB.createOperations( "collectionId", "", "documentId", "", "data", Map.of( - "name", "Walter O'Brien" + "embeddings", List.of(0.12, -0.55, 0.88, 1.02), + "metadata", Map.of( + "name", "First document" + ) ) )), // operations (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/java/vectorsdb/create-query.md b/docs/examples/java/vectorsdb/create-query.md index 3f1cfd5c..8c798724 100644 --- a/docs/examples/java/vectorsdb/create-query.md +++ b/docs/examples/java/vectorsdb/create-query.md @@ -17,7 +17,7 @@ vectorsDB.createQuery( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/create-transaction.md b/docs/examples/java/vectorsdb/create-transaction.md index 4784fc47..916f73c1 100644 --- a/docs/examples/java/vectorsdb/create-transaction.md +++ b/docs/examples/java/vectorsdb/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) VectorsDB vectorsDB = new VectorsDB(client); vectorsDB.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/list-documents.md b/docs/examples/java/vectorsdb/list-documents.md index 12ef1f0c..49689d36 100644 --- a/docs/examples/java/vectorsdb/list-documents.md +++ b/docs/examples/java/vectorsdb/list-documents.md @@ -17,7 +17,7 @@ vectorsDB.listDocuments( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/list-transactions.md b/docs/examples/java/vectorsdb/list-transactions.md index 18ef0e55..be8905e2 100644 --- a/docs/examples/java/vectorsdb/list-transactions.md +++ b/docs/examples/java/vectorsdb/list-transactions.md @@ -13,6 +13,7 @@ VectorsDB vectorsDB = new VectorsDB(client); vectorsDB.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/kotlin/account/create-email-password-session.md b/docs/examples/kotlin/account/create-email-password-session.md index 6100e483..aaceb65f 100644 --- a/docs/examples/kotlin/account/create-email-password-session.md +++ b/docs/examples/kotlin/account/create-email-password-session.md @@ -12,5 +12,6 @@ val account = Account(client) val result = account.createEmailPasswordSession( email = "email@example.com", password = "password", + duration = 60, // (optional) ) ``` diff --git a/docs/examples/kotlin/account/delete-sessions.md b/docs/examples/kotlin/account/delete-sessions.md index ff404dbc..96bbc341 100644 --- a/docs/examples/kotlin/account/delete-sessions.md +++ b/docs/examples/kotlin/account/delete-sessions.md @@ -9,5 +9,7 @@ val client = Client(context) val account = Account(client) -val result = account.deleteSessions() +val result = account.deleteSessions( + current = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/account/list-sessions.md b/docs/examples/kotlin/account/list-sessions.md index 8c261a2d..38e7e556 100644 --- a/docs/examples/kotlin/account/list-sessions.md +++ b/docs/examples/kotlin/account/list-sessions.md @@ -9,5 +9,7 @@ val client = Client(context) val account = Account(client) -val result = account.listSessions() +val result = account.listSessions( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/apps/create-installation-token.md b/docs/examples/kotlin/apps/create-installation-token.md new file mode 100644 index 00000000..6226e775 --- /dev/null +++ b/docs/examples/kotlin/apps/create-installation-token.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.createInstallationToken( + appId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/apps/create-key.md b/docs/examples/kotlin/apps/create-key.md new file mode 100644 index 00000000..d6daef84 --- /dev/null +++ b/docs/examples/kotlin/apps/create-key.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.createKey( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/create-secret.md b/docs/examples/kotlin/apps/create-secret.md new file mode 100644 index 00000000..d9a4c525 --- /dev/null +++ b/docs/examples/kotlin/apps/create-secret.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.createSecret( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/create.md b/docs/examples/kotlin/apps/create.md new file mode 100644 index 00000000..2299c031 --- /dev/null +++ b/docs/examples/kotlin/apps/create.md @@ -0,0 +1,33 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.create( + appId = "", + name = "", + redirectUris = listOf(), + description = "", // (optional) + clientUri = "https://example.com", // (optional) + logoUri = "https://example.com", // (optional) + privacyPolicyUrl = "https://example.com", // (optional) + termsUrl = "https://example.com", // (optional) + contacts = listOf(), // (optional) + tagline = "", // (optional) + tags = listOf(), // (optional) + images = listOf(), // (optional) + supportUrl = "https://example.com", // (optional) + dataDeletionUrl = "https://example.com", // (optional) + postLogoutRedirectUris = listOf(), // (optional) + enabled = false, // (optional) + type = "public", // (optional) + deviceFlow = false, // (optional) + teamId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/delete-installation.md b/docs/examples/kotlin/apps/delete-installation.md new file mode 100644 index 00000000..c07fee53 --- /dev/null +++ b/docs/examples/kotlin/apps/delete-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteInstallation( + appId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete-key.md b/docs/examples/kotlin/apps/delete-key.md new file mode 100644 index 00000000..3ad3825a --- /dev/null +++ b/docs/examples/kotlin/apps/delete-key.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteKey( + appId = "", + keyId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete-secret.md b/docs/examples/kotlin/apps/delete-secret.md new file mode 100644 index 00000000..ad22d5e0 --- /dev/null +++ b/docs/examples/kotlin/apps/delete-secret.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteSecret( + appId = "", + secretId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete-tokens.md b/docs/examples/kotlin/apps/delete-tokens.md new file mode 100644 index 00000000..e8d89009 --- /dev/null +++ b/docs/examples/kotlin/apps/delete-tokens.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteTokens( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete.md b/docs/examples/kotlin/apps/delete.md new file mode 100644 index 00000000..412b55f6 --- /dev/null +++ b/docs/examples/kotlin/apps/delete.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.delete( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get-installation.md b/docs/examples/kotlin/apps/get-installation.md new file mode 100644 index 00000000..288f21d6 --- /dev/null +++ b/docs/examples/kotlin/apps/get-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.getInstallation( + appId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get-key.md b/docs/examples/kotlin/apps/get-key.md new file mode 100644 index 00000000..8af73116 --- /dev/null +++ b/docs/examples/kotlin/apps/get-key.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.getKey( + appId = "", + keyId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get-secret.md b/docs/examples/kotlin/apps/get-secret.md new file mode 100644 index 00000000..a76bbe4e --- /dev/null +++ b/docs/examples/kotlin/apps/get-secret.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.getSecret( + appId = "", + secretId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get.md b/docs/examples/kotlin/apps/get.md new file mode 100644 index 00000000..3fc30c71 --- /dev/null +++ b/docs/examples/kotlin/apps/get.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.get( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/list-installation-scopes.md b/docs/examples/kotlin/apps/list-installation-scopes.md new file mode 100644 index 00000000..bafacf81 --- /dev/null +++ b/docs/examples/kotlin/apps/list-installation-scopes.md @@ -0,0 +1,13 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listInstallationScopes() +``` diff --git a/docs/examples/kotlin/apps/list-installations.md b/docs/examples/kotlin/apps/list-installations.md new file mode 100644 index 00000000..0819d5fc --- /dev/null +++ b/docs/examples/kotlin/apps/list-installations.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listInstallations( + appId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/list-keys.md b/docs/examples/kotlin/apps/list-keys.md new file mode 100644 index 00000000..9977dfe7 --- /dev/null +++ b/docs/examples/kotlin/apps/list-keys.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listKeys( + appId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/list-o-auth-2-scopes.md b/docs/examples/kotlin/apps/list-o-auth-2-scopes.md new file mode 100644 index 00000000..b0852955 --- /dev/null +++ b/docs/examples/kotlin/apps/list-o-auth-2-scopes.md @@ -0,0 +1,13 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listOAuth2Scopes() +``` diff --git a/docs/examples/kotlin/apps/list-secrets.md b/docs/examples/kotlin/apps/list-secrets.md new file mode 100644 index 00000000..aa69cc8c --- /dev/null +++ b/docs/examples/kotlin/apps/list-secrets.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listSecrets( + appId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/list.md b/docs/examples/kotlin/apps/list.md new file mode 100644 index 00000000..d2faad48 --- /dev/null +++ b/docs/examples/kotlin/apps/list.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.list( + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/update-team.md b/docs/examples/kotlin/apps/update-team.md new file mode 100644 index 00000000..b6f69443 --- /dev/null +++ b/docs/examples/kotlin/apps/update-team.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.updateTeam( + appId = "", + teamId = "", +) +``` diff --git a/docs/examples/kotlin/apps/update.md b/docs/examples/kotlin/apps/update.md new file mode 100644 index 00000000..b2bad705 --- /dev/null +++ b/docs/examples/kotlin/apps/update.md @@ -0,0 +1,34 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.update( + appId = "", + name = "", + description = "", // (optional) + clientUri = "https://example.com", // (optional) + logoUri = "https://example.com", // (optional) + privacyPolicyUrl = "https://example.com", // (optional) + termsUrl = "https://example.com", // (optional) + contacts = listOf(), // (optional) + tagline = "", // (optional) + tags = listOf(), // (optional) + images = listOf(), // (optional) + supportUrl = "https://example.com", // (optional) + dataDeletionUrl = "https://example.com", // (optional) + enabled = false, // (optional) + redirectUris = listOf(), // (optional) + postLogoutRedirectUris = listOf(), // (optional) + type = "public", // (optional) + deviceFlow = false, // (optional) + installationScopes = listOf(), // (optional) + installationRedirectUrl = "https://example.com", // (optional) +) +``` diff --git a/docs/examples/kotlin/avatars/delete-photo.md b/docs/examples/kotlin/avatars/delete-photo.md new file mode 100644 index 00000000..e16f4173 --- /dev/null +++ b/docs/examples/kotlin/avatars/delete-photo.md @@ -0,0 +1,13 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val avatars = Avatars(client) + +val result = avatars.deletePhoto() +``` diff --git a/docs/examples/kotlin/avatars/get-screenshot.md b/docs/examples/kotlin/avatars/get-screenshot.md index 1093c060..fcfb36c8 100644 --- a/docs/examples/kotlin/avatars/get-screenshot.md +++ b/docs/examples/kotlin/avatars/get-screenshot.md @@ -16,12 +16,11 @@ val avatars = Avatars(client) val result = avatars.getScreenshot( url = "https://example.com", headers = mapOf( - "Authorization" to "Bearer token123", - "X-Custom-Header" to "value" + "Accept-Language" to "en-US,en;q=0.9" ), // (optional) viewportWidth = 1920, // (optional) viewportHeight = 1080, // (optional) - scale = 2, // (optional) + scale = 2.0, // (optional) theme = BrowserTheme.LIGHT, // (optional) userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15", // (optional) fullpage = true, // (optional) @@ -29,7 +28,7 @@ val result = avatars.getScreenshot( timezone = Timezone.AFRICA_ABIDJAN, // (optional) latitude = 37.7749, // (optional) longitude = -122.4194, // (optional) - accuracy = 100, // (optional) + accuracy = 100.0, // (optional) touch = true, // (optional) permissions = BrowserPermission.GEOLOCATION, // (optional) sleep = 3, // (optional) diff --git a/docs/examples/kotlin/avatars/update-photo.md b/docs/examples/kotlin/avatars/update-photo.md new file mode 100644 index 00000000..326fe57a --- /dev/null +++ b/docs/examples/kotlin/avatars/update-photo.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.models.InputFile +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val avatars = Avatars(client) + +val result = avatars.updatePhoto( + file = InputFile.fromPath("file.png"), +) +``` diff --git a/docs/examples/kotlin/databases/decrement-document-attribute.md b/docs/examples/kotlin/databases/decrement-document-attribute.md index c32a6002..07452b01 100644 --- a/docs/examples/kotlin/databases/decrement-document-attribute.md +++ b/docs/examples/kotlin/databases/decrement-document-attribute.md @@ -14,8 +14,8 @@ val result = databases.decrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - min = 0, // (optional) + value = 1.0, // (optional) + min = 0.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/databases/increment-document-attribute.md b/docs/examples/kotlin/databases/increment-document-attribute.md index daaa6d6a..e71fddc7 100644 --- a/docs/examples/kotlin/databases/increment-document-attribute.md +++ b/docs/examples/kotlin/databases/increment-document-attribute.md @@ -14,8 +14,8 @@ val result = databases.incrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - max = 100, // (optional) + value = 1.0, // (optional) + max = 100.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/databases/list-transactions.md b/docs/examples/kotlin/databases/list-transactions.md index 3cabd79f..e4cd4633 100644 --- a/docs/examples/kotlin/databases/list-transactions.md +++ b/docs/examples/kotlin/databases/list-transactions.md @@ -11,5 +11,6 @@ val databases = Databases(client) val result = databases.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/create-documents.md b/docs/examples/kotlin/documentsdb/create-documents.md index 2923c100..ae6a5c6e 100644 --- a/docs/examples/kotlin/documentsdb/create-documents.md +++ b/docs/examples/kotlin/documentsdb/create-documents.md @@ -12,7 +12,14 @@ val documentsDB = DocumentsDB(client) val result = documentsDB.createDocuments( databaseId = "", collectionId = "", - documents = listOf(), + documents = listOf(mapOf( + "$id" to "example1", + "username" to "walter.obrien", + "email" to "walter.obrien@example.com", + "fullName" to "Walter O'Brien", + "age" to 30, + "isAdmin" to false + )), transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/decrement-document-attribute.md b/docs/examples/kotlin/documentsdb/decrement-document-attribute.md index fdb11fe0..461a2fe0 100644 --- a/docs/examples/kotlin/documentsdb/decrement-document-attribute.md +++ b/docs/examples/kotlin/documentsdb/decrement-document-attribute.md @@ -14,8 +14,8 @@ val result = documentsDB.decrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - min = 0, // (optional) + value = 1.0, // (optional) + min = 0.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/increment-document-attribute.md b/docs/examples/kotlin/documentsdb/increment-document-attribute.md index 8286ad44..0539e4a2 100644 --- a/docs/examples/kotlin/documentsdb/increment-document-attribute.md +++ b/docs/examples/kotlin/documentsdb/increment-document-attribute.md @@ -14,8 +14,8 @@ val result = documentsDB.incrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - max = 100, // (optional) + value = 1.0, // (optional) + max = 100.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/list-transactions.md b/docs/examples/kotlin/documentsdb/list-transactions.md index 46571468..a1a47a2a 100644 --- a/docs/examples/kotlin/documentsdb/list-transactions.md +++ b/docs/examples/kotlin/documentsdb/list-transactions.md @@ -11,5 +11,6 @@ val documentsDB = DocumentsDB(client) val result = documentsDB.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/docs/examples/kotlin/graphql/mutation.md b/docs/examples/kotlin/graphql/mutation.md index 0cdb78e6..076fc706 100644 --- a/docs/examples/kotlin/graphql/mutation.md +++ b/docs/examples/kotlin/graphql/mutation.md @@ -10,6 +10,8 @@ val client = Client(context) val graphql = Graphql(client) val result = graphql.mutation( - query = mapOf( "a" to "b" ), + query = mapOf( + "query" to "mutation { accountUpdateName(name: "Walter") { name } }" + ), ) ``` diff --git a/docs/examples/kotlin/graphql/query.md b/docs/examples/kotlin/graphql/query.md index 3359822a..cd0326da 100644 --- a/docs/examples/kotlin/graphql/query.md +++ b/docs/examples/kotlin/graphql/query.md @@ -10,6 +10,8 @@ val client = Client(context) val graphql = Graphql(client) val result = graphql.query( - query = mapOf( "a" to "b" ), + query = mapOf( + "query" to "query { localeGet { ip } }" + ), ) ``` diff --git a/docs/examples/kotlin/locale/list-codes.md b/docs/examples/kotlin/locale/list-codes.md index d7052144..31a89fc9 100644 --- a/docs/examples/kotlin/locale/list-codes.md +++ b/docs/examples/kotlin/locale/list-codes.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCodes() +val result = locale.listCodes( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-continents.md b/docs/examples/kotlin/locale/list-continents.md index 03e917e4..a5ce1c20 100644 --- a/docs/examples/kotlin/locale/list-continents.md +++ b/docs/examples/kotlin/locale/list-continents.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listContinents() +val result = locale.listContinents( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-countries-eu.md b/docs/examples/kotlin/locale/list-countries-eu.md index a933ed85..9214428e 100644 --- a/docs/examples/kotlin/locale/list-countries-eu.md +++ b/docs/examples/kotlin/locale/list-countries-eu.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCountriesEU() +val result = locale.listCountriesEU( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-countries-phones.md b/docs/examples/kotlin/locale/list-countries-phones.md index 8d4c7534..50355d79 100644 --- a/docs/examples/kotlin/locale/list-countries-phones.md +++ b/docs/examples/kotlin/locale/list-countries-phones.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCountriesPhones() +val result = locale.listCountriesPhones( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-countries.md b/docs/examples/kotlin/locale/list-countries.md index e67b5fe7..2a762a53 100644 --- a/docs/examples/kotlin/locale/list-countries.md +++ b/docs/examples/kotlin/locale/list-countries.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCountries() +val result = locale.listCountries( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-currencies.md b/docs/examples/kotlin/locale/list-currencies.md index fb0087d7..a25d489b 100644 --- a/docs/examples/kotlin/locale/list-currencies.md +++ b/docs/examples/kotlin/locale/list-currencies.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCurrencies() +val result = locale.listCurrencies( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-languages.md b/docs/examples/kotlin/locale/list-languages.md index 30fb65bf..acaac2e9 100644 --- a/docs/examples/kotlin/locale/list-languages.md +++ b/docs/examples/kotlin/locale/list-languages.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listLanguages() +val result = locale.listLanguages( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/oauth2/approve.md b/docs/examples/kotlin/oauth2/approve.md new file mode 100644 index 00000000..6d2a0a4d --- /dev/null +++ b/docs/examples/kotlin/oauth2/approve.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.approve( + grant_id = "", + authorization_details = "", // (optional) + scope = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/authorize-post.md b/docs/examples/kotlin/oauth2/authorize-post.md new file mode 100644 index 00000000..b80cabd5 --- /dev/null +++ b/docs/examples/kotlin/oauth2/authorize-post.md @@ -0,0 +1,28 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.authorizePost( + client_id = "", // (optional) + redirect_uri = "https://example.com", // (optional) + response_type = "", // (optional) + scope = "", // (optional) + state = "", // (optional) + nonce = "", // (optional) + code_challenge = "", // (optional) + code_challenge_method = "s256", // (optional) + prompt = "", // (optional) + max_age = 0, // (optional) + authorization_details = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) + request_uri = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/authorize.md b/docs/examples/kotlin/oauth2/authorize.md new file mode 100644 index 00000000..28c9db94 --- /dev/null +++ b/docs/examples/kotlin/oauth2/authorize.md @@ -0,0 +1,28 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.authorize( + client_id = "", // (optional) + redirect_uri = "https://example.com", // (optional) + response_type = "", // (optional) + scope = "", // (optional) + state = "", // (optional) + nonce = "", // (optional) + code_challenge = "", // (optional) + code_challenge_method = "s256", // (optional) + prompt = "", // (optional) + max_age = 0, // (optional) + authorization_details = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) + request_uri = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/create-device-authorization.md b/docs/examples/kotlin/oauth2/create-device-authorization.md new file mode 100644 index 00000000..6d5e4a34 --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-device-authorization.md @@ -0,0 +1,19 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createDeviceAuthorization( + client_id = "", // (optional) + scope = "", // (optional) + authorization_details = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/create-grant.md b/docs/examples/kotlin/oauth2/create-grant.md new file mode 100644 index 00000000..bbc2c53c --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-grant.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createGrant( + user_code = "", +) +``` diff --git a/docs/examples/kotlin/oauth2/create-par.md b/docs/examples/kotlin/oauth2/create-par.md new file mode 100644 index 00000000..3fb2ec0c --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-par.md @@ -0,0 +1,27 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createPAR( + client_id = "", + redirect_uri = "https://example.com", + response_type = "code", + scope = "", // (optional) + state = "", // (optional) + nonce = "", // (optional) + code_challenge = "", // (optional) + code_challenge_method = "s256", // (optional) + prompt = "", // (optional) + max_age = 0, // (optional) + authorization_details = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/create-token.md b/docs/examples/kotlin/oauth2/create-token.md new file mode 100644 index 00000000..ec03c423 --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-token.md @@ -0,0 +1,24 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createToken( + grant_type = "", + code = "", // (optional) + refresh_token = "", // (optional) + device_code = "", // (optional) + client_id = "", // (optional) + client_secret = "", // (optional) + code_verifier = "", // (optional) + redirect_uri = "https://example.com", // (optional) + resource = "", // (optional) + audience = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/get-grant.md b/docs/examples/kotlin/oauth2/get-grant.md new file mode 100644 index 00000000..e0a823f5 --- /dev/null +++ b/docs/examples/kotlin/oauth2/get-grant.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.getGrant( + grant_id = "", +) +``` diff --git a/docs/examples/kotlin/oauth2/list-organizations.md b/docs/examples/kotlin/oauth2/list-organizations.md new file mode 100644 index 00000000..463ebec0 --- /dev/null +++ b/docs/examples/kotlin/oauth2/list-organizations.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.listOrganizations( + limit = 1, // (optional) + offset = 0, // (optional) + search = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/list-projects.md b/docs/examples/kotlin/oauth2/list-projects.md new file mode 100644 index 00000000..2105828f --- /dev/null +++ b/docs/examples/kotlin/oauth2/list-projects.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.listProjects( + limit = 1, // (optional) + offset = 0, // (optional) + search = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/logout-post.md b/docs/examples/kotlin/oauth2/logout-post.md new file mode 100644 index 00000000..ecb1a719 --- /dev/null +++ b/docs/examples/kotlin/oauth2/logout-post.md @@ -0,0 +1,20 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.logoutPost( + id_token_hint = "", // (optional) + logout_hint = "", // (optional) + client_id = "", // (optional) + post_logout_redirect_uri = "https://example.com", // (optional) + state = "", // (optional) + ui_locales = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/logout.md b/docs/examples/kotlin/oauth2/logout.md new file mode 100644 index 00000000..e2c13d2c --- /dev/null +++ b/docs/examples/kotlin/oauth2/logout.md @@ -0,0 +1,20 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.logout( + id_token_hint = "", // (optional) + logout_hint = "", // (optional) + client_id = "", // (optional) + post_logout_redirect_uri = "https://example.com", // (optional) + state = "", // (optional) + ui_locales = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/reject.md b/docs/examples/kotlin/oauth2/reject.md new file mode 100644 index 00000000..3c0459d3 --- /dev/null +++ b/docs/examples/kotlin/oauth2/reject.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.reject( + grant_id = "", +) +``` diff --git a/docs/examples/kotlin/oauth2/revoke.md b/docs/examples/kotlin/oauth2/revoke.md new file mode 100644 index 00000000..d960657f --- /dev/null +++ b/docs/examples/kotlin/oauth2/revoke.md @@ -0,0 +1,18 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.revoke( + token = "", + token_type_hint = "access_token", // (optional) + client_id = "", // (optional) + client_secret = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/storage/get-file-preview.md b/docs/examples/kotlin/storage/get-file-preview.md index e2d2851d..bd106928 100644 --- a/docs/examples/kotlin/storage/get-file-preview.md +++ b/docs/examples/kotlin/storage/get-file-preview.md @@ -21,7 +21,7 @@ val result = storage.getFilePreview( borderWidth = 0, // (optional) borderColor = "FFFFFF", // (optional) borderRadius = 0, // (optional) - opacity = 0, // (optional) + opacity = 0.0, // (optional) rotation = -360, // (optional) background = "FFFFFF", // (optional) output = ImageFormat.JPG, // (optional) diff --git a/docs/examples/kotlin/tablesdb/decrement-row-column.md b/docs/examples/kotlin/tablesdb/decrement-row-column.md index 157c3d11..b8288ad3 100644 --- a/docs/examples/kotlin/tablesdb/decrement-row-column.md +++ b/docs/examples/kotlin/tablesdb/decrement-row-column.md @@ -14,8 +14,8 @@ val result = tablesDB.decrementRowColumn( tableId = "", rowId = "", column = "", - value = 1, // (optional) - min = 0, // (optional) + value = 1.0, // (optional) + min = 0.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/tablesdb/increment-row-column.md b/docs/examples/kotlin/tablesdb/increment-row-column.md index 802c6a2b..1aae1ffc 100644 --- a/docs/examples/kotlin/tablesdb/increment-row-column.md +++ b/docs/examples/kotlin/tablesdb/increment-row-column.md @@ -14,8 +14,8 @@ val result = tablesDB.incrementRowColumn( tableId = "", rowId = "", column = "", - value = 1, // (optional) - max = 100, // (optional) + value = 1.0, // (optional) + max = 100.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/tablesdb/list-transactions.md b/docs/examples/kotlin/tablesdb/list-transactions.md index 03c9d5b4..b680ee15 100644 --- a/docs/examples/kotlin/tablesdb/list-transactions.md +++ b/docs/examples/kotlin/tablesdb/list-transactions.md @@ -11,5 +11,6 @@ val tablesDB = TablesDB(client) val result = tablesDB.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/docs/examples/kotlin/teams/create-membership.md b/docs/examples/kotlin/teams/create-membership.md index e5289e64..219119af 100644 --- a/docs/examples/kotlin/teams/create-membership.md +++ b/docs/examples/kotlin/teams/create-membership.md @@ -11,7 +11,7 @@ val teams = Teams(client) val result = teams.createMembership( teamId = "", - roles = listOf(), + roles = listOf("editor"), email = "email@example.com", // (optional) userId = "", // (optional) phone = "+12065550100", // (optional) diff --git a/docs/examples/kotlin/teams/update-membership.md b/docs/examples/kotlin/teams/update-membership.md index 6e721d4e..d9e39d7b 100644 --- a/docs/examples/kotlin/teams/update-membership.md +++ b/docs/examples/kotlin/teams/update-membership.md @@ -12,6 +12,6 @@ val teams = Teams(client) val result = teams.updateMembership( teamId = "", membershipId = "", - roles = listOf(), + roles = listOf("editor"), ) ``` diff --git a/docs/examples/kotlin/vectorsdb/create-operations.md b/docs/examples/kotlin/vectorsdb/create-operations.md index 1a2908af..8bd201a3 100644 --- a/docs/examples/kotlin/vectorsdb/create-operations.md +++ b/docs/examples/kotlin/vectorsdb/create-operations.md @@ -17,7 +17,10 @@ val result = vectorsDB.createOperations( "collectionId" to "", "documentId" to "", "data" to mapOf( - "name" to "Walter O'Brien" + "embeddings" to listOf(0.12, -0.55, 0.88, 1.02), + "metadata" to mapOf( + "name" to "First document" + ) ) )), // (optional) ) diff --git a/docs/examples/kotlin/vectorsdb/list-transactions.md b/docs/examples/kotlin/vectorsdb/list-transactions.md index 97981c01..76fb10e3 100644 --- a/docs/examples/kotlin/vectorsdb/list-transactions.md +++ b/docs/examples/kotlin/vectorsdb/list-transactions.md @@ -11,5 +11,6 @@ val vectorsDB = VectorsDB(client) val result = vectorsDB.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 2b339ac0..77c332ec 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -32,6 +32,16 @@ android { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } + + // The push service pulls in HiveMQ (Netty), whose jars ship duplicate META-INF + // metadata; drop the duplicates so the app can be packaged. + packaging { + resources { + excludes += "/META-INF/INDEX.LIST" + excludes += "/META-INF/io.netty.versions.properties" + excludes += "/META-INF/DEPENDENCIES" + } + } } dependencies { diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 45641217..91c8e07e 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -46,9 +46,11 @@ android { } defaultConfig { - minSdk = 23 + minSdk = 24 buildConfigField("String", "SDK_VERSION", "\"$publishVersion\"") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + // Push: R8 rules for the bundled MQTT client, applied to consuming apps. + consumerProguardFiles("consumer-rules.pro") } buildTypes { @@ -82,6 +84,7 @@ dependencies { api("com.squareup.okhttp3:okhttp:5.5.0") implementation("com.google.code.gson:gson:2.14.0") + implementation("com.hivemq:hivemq-mqtt-client:1.3.6") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0") implementation("androidx.lifecycle:lifecycle-common-java8:2.11.0") diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro new file mode 100644 index 00000000..bca84735 --- /dev/null +++ b/library/consumer-rules.pro @@ -0,0 +1,26 @@ +# Push: rules applied to apps that shrink with R8. +# +# The HiveMQ MQTT client bundles Netty, which references optional classes that are never loaded +# on Android: native transports, the HTTP/WebSocket codecs, proxy handlers, alternative TLS +# providers (tcnative, BouncyCastle, Conscrypt, Jetty ALPN) and logging backends. +-dontwarn io.netty.channel.epoll.** +-dontwarn io.netty.channel.kqueue.** +-dontwarn io.netty.handler.codec.http.** +-dontwarn io.netty.handler.proxy.** +-dontwarn io.netty.internal.tcnative.** +-dontwarn org.bouncycastle.** +-dontwarn org.conscrypt.** +-dontwarn org.eclipse.jetty.** +-dontwarn org.apache.log4j.** +-dontwarn org.apache.logging.log4j.** +-dontwarn org.slf4j.** +-dontwarn reactor.blockhound.** + +# Netty and JCTools reach their own fields by name, through atomic field updaters and Unsafe offsets. +-keepclassmembernames class io.netty.** { ; } +-keepclassmembers class org.jctools.** { ; } + +# Netty's leak detector looks these methods up by name when its classes load. +-keepclassmembers class io.netty.buffer.AbstractByteBufAllocator { *** toLeakAwareBuffer(...); } +-keepclassmembers class io.netty.buffer.AdvancedLeakAwareByteBuf { *** recordLeakNonRefCountingOperation(...); } +-keepclassmembers class io.netty.util.ReferenceCountUtil { *** touch(...); } diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 899321de..44f894ed 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -3,5 +3,39 @@ + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 40431217..c364e835 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -8,6 +8,7 @@ import io.appwrite.cookies.stores.SharedPreferencesCookieStore import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.fromJson import io.appwrite.extensions.toJsonRequestBody +import io.appwrite.extensions.toJsonWithNulls import io.appwrite.models.InputFile import io.appwrite.models.UploadProgress import kotlinx.coroutines.CoroutineScope @@ -49,7 +50,11 @@ class Client @JvmOverloads constructor( var endpoint: String = "https://cloud.appwrite.io/v1", @set:JvmSynthetic var endpointRealtime: String? = null, - private var selfSigned: Boolean = false + private var selfSigned: Boolean = false, + @set:JvmSynthetic + var endpointPush: String? = null, + @set:JvmSynthetic + var pushClientId: String? = null ) : CoroutineScope { companion object { @@ -96,7 +101,7 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "28.0.0", + "x-sdk-version" to "29.0.0", "x-appwrite-response-format" to "2.3.0" ) config = mutableMapOf() @@ -324,6 +329,37 @@ class Client @JvmOverloads constructor( return this } + /** + * Set push endpoint + * + * The MQTT broker URL the [io.appwrite.services.Push] service connects to, e.g. + * `mqtt://host:1883` or `mqtts://host:8883`. + * + * @param endpoint + * + * @return this + */ + fun setPushEndpoint(endpoint: String): Client { + this.endpointPush = endpoint + return this + } + + /** + * Set push client id + * + * A stable client id for the [io.appwrite.services.Push] service. The broker keys its + * offline-replay cursor on this id, so pass a stable value to resume replay across + * restarts. Defaults to the userId decoded from the JWT (else the credential) when unset. + * + * @param pushClientId + * + * @return this + */ + fun setPushClientId(pushClientId: String): Client { + this.pushClientId = pushClientId + return this + } + /** * Add Header * @@ -439,9 +475,12 @@ class Client @JvmOverloads constructor( filteredParams.forEach { when { - it.key == "file" -> { + it.value is MultipartBody.Part -> { builder.addPart(it.value as MultipartBody.Part) } + it.value is Map<*, *> -> { + builder.addFormDataPart(it.key, (it.value as Map<*, *>).toJsonWithNulls()) + } it.value is List<*> -> { val list = it.value as List<*> for (index in list.indices) { @@ -508,11 +547,21 @@ class Client @JvmOverloads constructor( headers: MutableMap, params: MutableMap, responseType: Class, - converter: ((Any) -> T), + converter: ((Any) -> T)? = null, paramName: String, idParamName: String? = null, onProgress: ((UploadProgress) -> Unit)? = null, ): T { + if (params[paramName] == null) { + return call( + method = "POST", + path, + headers, + params, + responseType, + converter + ) + } val input = params[paramName] as InputFile val size: Long = when (input.sourceType) { "path", "file" -> { @@ -524,7 +573,7 @@ class Client @JvmOverloads constructor( else -> throw UnsupportedOperationException() } - if (size < CHUNK_SIZE) { + if (size < CHUNK_SIZE || responseType == String::class.java) { val data = when (input.sourceType) { "file", "path" -> File(input.path).asRequestBody() "bytes" -> (input.data as ByteArray).toRequestBody(input.mimeType.toMediaType()) @@ -690,7 +739,7 @@ class Client @JvmOverloads constructor( result = completedResultRef.get() ?: lastResultRef.get() } - return converter(result as Map) + return converter?.invoke(result as Map) ?: result as T } /** diff --git a/library/src/main/java/io/appwrite/ID.kt b/library/src/main/java/io/appwrite/ID.kt index cf415258..93f948fc 100644 --- a/library/src/main/java/io/appwrite/ID.kt +++ b/library/src/main/java/io/appwrite/ID.kt @@ -1,6 +1,5 @@ package io.appwrite -import java.time.Instant import kotlin.math.floor import kotlin.random.Random @@ -12,8 +11,7 @@ class ID { // Generate an hex ID based on timestamp // Recreated from https://www.php.net/manual/en/function.uniqid.php private fun hexTimestamp(): String { - val now = Instant.now() - val sec = now.epochSecond + val sec = System.currentTimeMillis() / 1000 val usec = (System.nanoTime() / 1000) % 1000 val hexTimestamp = "%08x%05x".format(sec, usec) diff --git a/library/src/main/java/io/appwrite/Operator.kt b/library/src/main/java/io/appwrite/Operator.kt index 578dc087..74ab2cb9 100644 --- a/library/src/main/java/io/appwrite/Operator.kt +++ b/library/src/main/java/io/appwrite/Operator.kt @@ -22,6 +22,13 @@ class Operator( ) { override fun toString() = this.toJson() + fun toMap(): Map = mapOf( + "method" to method, + "values" to values, + ).filterValues { it != null } + + fun toJson(): String = toMap().toJson() + companion object { fun increment(value: Number = 1, max: Number? = null): String { require(!value.toDouble().isNaN() && !value.toDouble().isInfinite()) { "Value cannot be NaN or Infinity" } diff --git a/library/src/main/java/io/appwrite/Query.kt b/library/src/main/java/io/appwrite/Query.kt index b0d4ab0f..23f100df 100644 --- a/library/src/main/java/io/appwrite/Query.kt +++ b/library/src/main/java/io/appwrite/Query.kt @@ -18,6 +18,14 @@ class Query( */ override fun toString() = this.toJson() + fun toMap(): Map = mapOf( + "method" to method, + "attribute" to attribute, + "values" to values, + ).filterValues { it != null } + + fun toJson(): String = toMap().toJson() + companion object { /** @@ -350,7 +358,7 @@ class Query( * @param queries The list of query strings to combine. * @returns The query string. */ - fun or(queries: List) = Query("or", null, queries.map { it.fromJson() }).toJson() + fun or(queries: List) = Query("or", null, queries.map { it.fromJson>() }).toJson() /** * Combine multiple queries using logical AND operator. @@ -358,7 +366,7 @@ class Query( * @param queries The list of query strings to combine. * @returns The query string. */ - fun and(queries: List) = Query("and", null, queries.map { it.fromJson() }).toJson() + fun and(queries: List) = Query("and", null, queries.map { it.fromJson>() }).toJson() /** * Filter array elements where at least one element matches all the specified queries. @@ -367,7 +375,7 @@ class Query( * @param queries The list of query strings to match against array elements. * @returns The query string. */ - fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson() }).toJson() + fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson>() }).toJson() /** * Filter resources where attribute is at a specific distance from the given coordinates. diff --git a/library/src/main/java/io/appwrite/Topic.kt b/library/src/main/java/io/appwrite/Topic.kt new file mode 100644 index 00000000..8945da0e --- /dev/null +++ b/library/src/main/java/io/appwrite/Topic.kt @@ -0,0 +1,68 @@ +package io.appwrite + +/** + * Validate the levels passed to `path()`: a non-empty list of non-empty strings, none holding + * the level separator `/` or the wildcards `+` and `#` (use `any()` and `all()` for those). + */ +private fun levelsOf(levels: List): List { + require(levels.isNotEmpty()) { "path() needs at least one level" } + levels.forEachIndexed { index, level -> + require(level.isNotEmpty()) { "empty level at index $index" } + require(!level.contains("/")) { + val parts = level.split("/").joinToString(", ") { "'$it'" } + "\"$level\" contains \"/\", split it: path([$parts])" + } + require(level != "+") { "\"+\" is reserved, use any()" } + require(!level.contains("+")) { "\"$level\" contains \"+\", use any() for wildcards" } + require(level != "#") { "\"#\" is reserved, use all()" } + require(!level.contains("#")) { "\"$level\" contains \"#\", use all() for wildcards" } + } + return levels.toList() +} + +/** + * Helper to build MQTT topics for [services.Push], level by level. + * + * ``` + * Topic.path(listOf("user", userId, "notification")) // "user/123/notification" + * Topic.path(listOf("user")).any().path(listOf("notification")) // "user/+/notification" + * Topic.path(listOf("org")).any().path(listOf("logs")).all() // "org/+/logs/#" + * ``` + * + * `any()` matches exactly one level and `all()` everything below, at any depth (including the + * parent itself). `all()` ends the topic, so it returns a [ResolvedTopic]. + * + * @throws IllegalArgumentException from `path()` when a level is empty or holds `/`, `+` or `#`. + */ +class Topic private constructor( + private val levels: List, +) { + /** Append the given levels. */ + fun path(levels: List): Topic = Topic(this.levels + levelsOf(levels)) + + /** Append a single-level wildcard (`+`): matches exactly one level. */ + fun any(): Topic = Topic(levels + "+") + + /** Append the multi-level wildcard (`#`) and end the topic. */ + fun all(): ResolvedTopic = ResolvedTopic(levels + "#") + + override fun toString(): String = levels.joinToString("/") + + companion object { + /** Start a topic with the given levels, e.g. `Topic.path(listOf("user", userId))`. */ + fun path(levels: List): Topic = Topic(levelsOf(levels)) + + /** Start a topic with a single-level wildcard (`+`). */ + fun any(): Topic = Topic(listOf("+")) + + /** The multi-level wildcard alone (`#`): matches every topic. */ + fun all(): ResolvedTopic = ResolvedTopic(listOf("#")) + } +} + +/** A topic ended by `all()`: nothing can be appended after the `#` wildcard. */ +class ResolvedTopic internal constructor( + private val levels: List, +) { + override fun toString(): String = levels.joinToString("/") +} diff --git a/library/src/main/java/io/appwrite/cookies/InternalCookie.kt b/library/src/main/java/io/appwrite/cookies/InternalCookie.kt index dfb8948a..6a1422b1 100644 --- a/library/src/main/java/io/appwrite/cookies/InternalCookie.kt +++ b/library/src/main/java/io/appwrite/cookies/InternalCookie.kt @@ -31,6 +31,38 @@ data class InternalCookie( cookie.version ) + companion object { + fun from(map: Map<*, *>) = InternalCookie( + comment = map["comment"] as? String, + commentURL = map["commentURL"] as? String, + discard = map["discard"] as? Boolean, + domain = map["domain"] as String, + maxAge = (map["maxAge"] as? Number)?.toLong(), + name = map["name"] as String, + path = map["path"] as? String, + portlist = map["portlist"] as? String, + secure = map["secure"] as? Boolean, + value = map["value"] as String, + version = (map["version"] as? Number)?.toInt(), + httpOnly = map["httpOnly"] as? Boolean, + ) + } + + fun toMap(): Map = mapOf( + "comment" to comment, + "commentURL" to commentURL, + "discard" to discard, + "domain" to domain, + "maxAge" to maxAge, + "name" to name, + "path" to path, + "portlist" to portlist, + "secure" to secure, + "value" to value, + "version" to version, + "httpOnly" to httpOnly, + ) + fun toHttpCookie() = HttpCookie(name, value).apply { comment = this@InternalCookie.comment commentURL = this@InternalCookie.commentURL diff --git a/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt b/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt index 61986f6c..85d97e0f 100644 --- a/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt +++ b/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt @@ -4,7 +4,6 @@ import android.content.SharedPreferences import android.os.Build import android.util.Log import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import io.appwrite.cookies.InternalCookie import java.net.HttpCookie import java.net.URI @@ -20,9 +19,8 @@ open class SharedPreferencesCookieStore( preferences.all.forEach { (key, value) -> try { val index = URI.create(key) - val listType = object : TypeToken>() {}.type - val internalCookies = - gson.fromJson>(value.toString(), listType) + val internalCookies = gson.fromJson(value.toString(), List::class.java) + .map { InternalCookie.from(it as Map<*, *>) } val cookies = internalCookies.map { it.toHttpCookie() }.toMutableList() uriIndex[index] = cookies } catch (exception: Throwable) { @@ -59,8 +57,7 @@ open class SharedPreferencesCookieStore( } } - val listType = object : TypeToken>() {}.type - val json = gson.toJson(internalCookies, listType) + val json = gson.toJson(internalCookies.map { it.toMap() }) preferences .edit() @@ -82,8 +79,7 @@ open class SharedPreferencesCookieStore( } } } - val listType = object : TypeToken>() {}.type - val json = gson.toJson(internalCookies, listType) + val json = gson.toJson(internalCookies?.map { it.toMap() }) preferences.edit().apply { when (cookies) { diff --git a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt index 4eb5cbfd..265e7807 100644 --- a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt +++ b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt @@ -87,6 +87,8 @@ enum class OAuthProvider(val value: String) { TRADESHIFTBOX("tradeshiftBox"), @SerializedName("twitch") TWITCH("twitch"), + @SerializedName("webflow") + WEBFLOW("webflow"), @SerializedName("wordpress") WORDPRESS("wordpress"), @SerializedName("x") diff --git a/library/src/main/java/io/appwrite/models/Account.kt b/library/src/main/java/io/appwrite/models/Account.kt new file mode 100644 index 00000000..38865ff8 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Account.kt @@ -0,0 +1,266 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Account + */ +data class Account( + /** + * User ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * User creation date in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * User update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * User name. + */ + @SerializedName("name") + val name: String, + + /** + * User registration date in ISO 8601 format. + */ + @SerializedName("registration") + val registration: String, + + /** + * User status. Pass `true` for enabled and `false` for disabled. + */ + @SerializedName("status") + val status: Boolean, + + /** + * Labels for the user. + */ + @SerializedName("labels") + val labels: List, + + /** + * Password update time in ISO 8601 format. + */ + @SerializedName("passwordUpdate") + val passwordUpdate: String, + + /** + * User email address. + */ + @SerializedName("email") + val email: String, + + /** + * User phone number in E.164 format. + */ + @SerializedName("phone") + val phone: String, + + /** + * Email verification status. + */ + @SerializedName("emailVerification") + val emailVerification: Boolean, + + /** + * Canonical form of the user email address. + */ + @SerializedName("emailCanonical") + var emailCanonical: String?, + + /** + * Whether the user email is from a free email provider. + */ + @SerializedName("emailIsFree") + var emailIsFree: Boolean?, + + /** + * Whether the user email is from a disposable email provider. + */ + @SerializedName("emailIsDisposable") + var emailIsDisposable: Boolean?, + + /** + * Whether the user email is from a corporate domain. + */ + @SerializedName("emailIsCorporate") + var emailIsCorporate: Boolean?, + + /** + * Whether the user email is in its canonical form. + */ + @SerializedName("emailIsCanonical") + var emailIsCanonical: Boolean?, + + /** + * Whether the password was found in a known data breach the last time it was checked. Null when the password has never been checked. + */ + @SerializedName("passwordPwned") + var passwordPwned: Boolean?, + + /** + * Phone verification status. + */ + @SerializedName("phoneVerification") + val phoneVerification: Boolean, + + /** + * Multi factor authentication status. + */ + @SerializedName("mfa") + val mfa: Boolean, + + /** + * User preferences as a key-value object + */ + @SerializedName("prefs") + val prefs: Preferences, + + /** + * A user-owned message receiver. A single user may have multiple e.g. emails, phones, and a browser. Each target is registered with a single provider. + */ + @SerializedName("targets") + val targets: List, + + /** + * Most recent access date in ISO 8601 format. This attribute is only updated again after 24 hours. + */ + @SerializedName("accessedAt") + val accessedAt: String, + + /** + * Whether the user can impersonate other users. + */ + @SerializedName("impersonator") + var impersonator: Boolean?, + + /** + * ID of the original actor performing the impersonation. Present only when the current request is impersonating another user. Internal audit logs attribute the action to this user, while the impersonated target is recorded only in internal audit payload data. + */ + @SerializedName("impersonatorUserId") + var impersonatorUserId: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "name" to name as Any, + "registration" to registration as Any, + "status" to status as Any, + "labels" to labels as Any, + "passwordUpdate" to passwordUpdate as Any, + "email" to email as Any, + "phone" to phone as Any, + "emailVerification" to emailVerification as Any, + "emailCanonical" to emailCanonical as Any?, + "emailIsFree" to emailIsFree as Any?, + "emailIsDisposable" to emailIsDisposable as Any?, + "emailIsCorporate" to emailIsCorporate as Any?, + "emailIsCanonical" to emailIsCanonical as Any?, + "passwordPwned" to passwordPwned as Any?, + "phoneVerification" to phoneVerification as Any, + "mfa" to mfa as Any, + "prefs" to prefs.toMap() as Any, + "targets" to targets.map { it.toMap() } as Any, + "accessedAt" to accessedAt as Any, + "impersonator" to impersonator as Any?, + "impersonatorUserId" to impersonatorUserId as Any?, + ) + + companion object { + operator fun invoke( + id: String, + createdAt: String, + updatedAt: String, + name: String, + registration: String, + status: Boolean, + labels: List, + passwordUpdate: String, + email: String, + phone: String, + emailVerification: Boolean, + emailCanonical: String?, + emailIsFree: Boolean?, + emailIsDisposable: Boolean?, + emailIsCorporate: Boolean?, + emailIsCanonical: Boolean?, + passwordPwned: Boolean?, + phoneVerification: Boolean, + mfa: Boolean, + prefs: Preferences>, + targets: List, + accessedAt: String, + impersonator: Boolean?, + impersonatorUserId: String?, + ) = Account>( + id, + createdAt, + updatedAt, + name, + registration, + status, + labels, + passwordUpdate, + email, + phone, + emailVerification, + emailCanonical, + emailIsFree, + emailIsDisposable, + emailIsCorporate, + emailIsCanonical, + passwordPwned, + phoneVerification, + mfa, + prefs, + targets, + accessedAt, + impersonator, + impersonatorUserId, + ) + + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + nestedType: Class + ) = Account( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + name = map["name"] as String, + registration = map["registration"] as String, + status = map["status"] as Boolean, + labels = map["labels"] as List, + passwordUpdate = map["passwordUpdate"] as String, + email = map["email"] as String, + phone = map["phone"] as String, + emailVerification = map["emailVerification"] as Boolean, + emailCanonical = map["emailCanonical"] as? String, + emailIsFree = map["emailIsFree"] as? Boolean, + emailIsDisposable = map["emailIsDisposable"] as? Boolean, + emailIsCorporate = map["emailIsCorporate"] as? Boolean, + emailIsCanonical = map["emailIsCanonical"] as? Boolean, + passwordPwned = map["passwordPwned"] as? Boolean, + phoneVerification = map["phoneVerification"] as Boolean, + mfa = map["mfa"] as Boolean, + prefs = Preferences.from(map = map["prefs"] as Map, nestedType), + targets = (map["targets"] as List>).map { Target.from(map = it) }, + accessedAt = map["accessedAt"] as String, + impersonator = map["impersonator"] as? Boolean, + impersonatorUserId = map["impersonatorUserId"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/App.kt b/library/src/main/java/io/appwrite/models/App.kt new file mode 100644 index 00000000..f4a715c8 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/App.kt @@ -0,0 +1,229 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App + */ +data class App( + /** + * App ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * App creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * App update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application name. + */ + @SerializedName("name") + val name: String, + + /** + * Application description shown to users during OAuth2 consent. + */ + @SerializedName("description") + val description: String, + + /** + * Application homepage URL shown to users during OAuth2 consent. + */ + @SerializedName("clientUri") + val clientUri: String, + + /** + * Application logo URL shown to users during OAuth2 consent. + */ + @SerializedName("logoUri") + val logoUri: String, + + /** + * Application privacy policy URL shown to users during OAuth2 consent. + */ + @SerializedName("privacyPolicyUrl") + val privacyPolicyUrl: String, + + /** + * Application terms of service URL shown to users during OAuth2 consent. + */ + @SerializedName("termsUrl") + val termsUrl: String, + + /** + * Application support or security contact emails. + */ + @SerializedName("contacts") + val contacts: List, + + /** + * Application tagline shown to users during OAuth2 consent. + */ + @SerializedName("tagline") + val tagline: String, + + /** + * Application tags shown to users during OAuth2 consent. + */ + @SerializedName("tags") + val tags: List, + + /** + * Application labels. Read-only for clients; only a server SDK using a project API key can update them. + */ + @SerializedName("labels") + val labels: List, + + /** + * Application image URLs shown to users during OAuth2 consent. + */ + @SerializedName("images") + val images: List, + + /** + * Application support URL shown to users during OAuth2 consent. + */ + @SerializedName("supportUrl") + val supportUrl: String, + + /** + * Application data deletion URL shown to users during OAuth2 consent. + */ + @SerializedName("dataDeletionUrl") + val dataDeletionUrl: String, + + /** + * List of authorized redirect URIs. These URIs can be used to redirect users after they authenticate. + */ + @SerializedName("redirectUris") + val redirectUris: List, + + /** + * List of authorized post-logout redirect URIs for OpenID Connect RP-Initiated Logout. The logout endpoint only redirects users to URIs in this list after ending their session. + */ + @SerializedName("postLogoutRedirectUris") + val postLogoutRedirectUris: List, + + /** + * Whether the app is enabled or not. + */ + @SerializedName("enabled") + val enabled: Boolean, + + /** + * OAuth2 client type. `public` for SPAs, mobile, and native apps that cannot keep a client secret (PKCE required); `confidential` for server-side clients that authenticate with a client secret. + */ + @SerializedName("type") + val type: String, + + /** + * Whether this client may use the OAuth2 Device Authorization Grant (RFC 8628). + */ + @SerializedName("deviceFlow") + val deviceFlow: Boolean, + + /** + * ID of team that owns the application, if owned by team. Otherwise, user ID will be used. + */ + @SerializedName("teamId") + val teamId: String, + + /** + * ID of user who owns the application, if owned by user. Otherwise, team ID will be used. + */ + @SerializedName("userId") + val userId: String, + + /** + * Scopes the application requests when installed on a team. Organization-level and project-level scopes only. + */ + @SerializedName("installationScopes") + val installationScopes: List, + + /** + * URL users are redirected to after creating or updating an installation of this application. Empty for no redirect. + */ + @SerializedName("installationRedirectUrl") + val installationRedirectUrl: String, + + /** + * List of application secrets. + */ + @SerializedName("secrets") + val secrets: List, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "name" to name as Any, + "description" to description as Any, + "clientUri" to clientUri as Any, + "logoUri" to logoUri as Any, + "privacyPolicyUrl" to privacyPolicyUrl as Any, + "termsUrl" to termsUrl as Any, + "contacts" to contacts as Any, + "tagline" to tagline as Any, + "tags" to tags as Any, + "labels" to labels as Any, + "images" to images as Any, + "supportUrl" to supportUrl as Any, + "dataDeletionUrl" to dataDeletionUrl as Any, + "redirectUris" to redirectUris as Any, + "postLogoutRedirectUris" to postLogoutRedirectUris as Any, + "enabled" to enabled as Any, + "type" to type as Any, + "deviceFlow" to deviceFlow as Any, + "teamId" to teamId as Any, + "userId" to userId as Any, + "installationScopes" to installationScopes as Any, + "installationRedirectUrl" to installationRedirectUrl as Any, + "secrets" to secrets.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = App( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + name = map["name"] as String, + description = map["description"] as String, + clientUri = map["clientUri"] as String, + logoUri = map["logoUri"] as String, + privacyPolicyUrl = map["privacyPolicyUrl"] as String, + termsUrl = map["termsUrl"] as String, + contacts = map["contacts"] as List, + tagline = map["tagline"] as String, + tags = map["tags"] as List, + labels = map["labels"] as List, + images = map["images"] as List, + supportUrl = map["supportUrl"] as String, + dataDeletionUrl = map["dataDeletionUrl"] as String, + redirectUris = map["redirectUris"] as List, + postLogoutRedirectUris = map["postLogoutRedirectUris"] as List, + enabled = map["enabled"] as Boolean, + type = map["type"] as String, + deviceFlow = map["deviceFlow"] as Boolean, + teamId = map["teamId"] as String, + userId = map["userId"] as String, + installationScopes = map["installationScopes"] as List, + installationRedirectUrl = map["installationRedirectUrl"] as String, + secrets = (map["secrets"] as List>).map { AppSecret.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppKey.kt b/library/src/main/java/io/appwrite/models/AppKey.kt new file mode 100644 index 00000000..f59e8d50 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppKey.kt @@ -0,0 +1,93 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppKey + */ +data class AppKey( + /** + * App key ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * App key creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * App key update time in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application ID this app key belongs to. + */ + @SerializedName("appId") + val appId: String, + + /** + * App key secret. + */ + @SerializedName("secret") + val secret: String, + + /** + * Last few characters of the app key secret, used to help identify it. + */ + @SerializedName("hint") + val hint: String, + + /** + * ID of the user who created the app key. + */ + @SerializedName("createdById") + val createdById: String, + + /** + * Name of the user who created the app key. + */ + @SerializedName("createdByName") + val createdByName: String, + + /** + * Time the app key was last used for authentication in ISO 8601 format. Null if never used. + */ + @SerializedName("lastAccessedAt") + var lastAccessedAt: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "appId" to appId as Any, + "secret" to secret as Any, + "hint" to hint as Any, + "createdById" to createdById as Any, + "createdByName" to createdByName as Any, + "lastAccessedAt" to lastAccessedAt as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppKey( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + appId = map["appId"] as String, + secret = map["secret"] as String, + hint = map["hint"] as String, + createdById = map["createdById"] as String, + createdByName = map["createdByName"] as String, + lastAccessedAt = map["lastAccessedAt"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppKeyList.kt b/library/src/main/java/io/appwrite/models/AppKeyList.kt new file mode 100644 index 00000000..ea4b1cca --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppKeyList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App keys list + */ +data class AppKeyList( + /** + * Total number of keys that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of keys. + */ + @SerializedName("keys") + val keys: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "keys" to keys.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppKeyList( + total = (map["total"] as Number).toLong(), + keys = (map["keys"] as List>).map { AppKey.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppScope.kt b/library/src/main/java/io/appwrite/models/AppScope.kt new file mode 100644 index 00000000..4cc7fe95 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppScope.kt @@ -0,0 +1,61 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppScope + */ +data class AppScope( + /** + * Scope value as requested by apps. + */ + @SerializedName("value") + val value: String, + + /** + * Human-readable description of what the scope grants. + */ + @SerializedName("description") + val description: String, + + /** + * What the scope grants access to. One of `account`, `project`, or `organization`. Only `project` and `organization` scopes are installable. + */ + @SerializedName("type") + val type: String, + + /** + * Scope category, used to group scopes on consent and installation screens. + */ + @SerializedName("category") + val category: String, + + /** + * Whether the scope is deprecated. Deprecated scopes can still be requested but should not be offered for new grants. + */ + @SerializedName("deprecated") + val deprecated: Boolean, + +) { + fun toMap(): Map = mapOf( + "value" to value as Any, + "description" to description as Any, + "type" to type as Any, + "category" to category as Any, + "deprecated" to deprecated as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppScope( + value = map["value"] as String, + description = map["description"] as String, + type = map["type"] as String, + category = map["category"] as String, + deprecated = map["deprecated"] as Boolean, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppScopeList.kt b/library/src/main/java/io/appwrite/models/AppScopeList.kt new file mode 100644 index 00000000..cbcf91fb --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppScopeList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App scopes list + */ +data class AppScopeList( + /** + * Total number of scopes that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of scopes. + */ + @SerializedName("scopes") + val scopes: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "scopes" to scopes.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppScopeList( + total = (map["total"] as Number).toLong(), + scopes = (map["scopes"] as List>).map { AppScope.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppSecret.kt b/library/src/main/java/io/appwrite/models/AppSecret.kt new file mode 100644 index 00000000..0349ffbd --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppSecret.kt @@ -0,0 +1,93 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppSecret + */ +data class AppSecret( + /** + * Secret ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Secret creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Secret update time in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application ID this secret belongs to. + */ + @SerializedName("appId") + val appId: String, + + /** + * Always empty. The application client secret is returned only once, in the response of the createSecret method. + */ + @SerializedName("secret") + val secret: String, + + /** + * Last few characters of the client secret, used to help identify it. + */ + @SerializedName("hint") + val hint: String, + + /** + * ID of the user who created the secret. + */ + @SerializedName("createdById") + val createdById: String, + + /** + * Name of the user who created the secret. + */ + @SerializedName("createdByName") + val createdByName: String, + + /** + * Time the secret was last used for authentication in ISO 8601 format. Null if never used. + */ + @SerializedName("lastAccessedAt") + var lastAccessedAt: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "appId" to appId as Any, + "secret" to secret as Any, + "hint" to hint as Any, + "createdById" to createdById as Any, + "createdByName" to createdByName as Any, + "lastAccessedAt" to lastAccessedAt as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppSecret( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + appId = map["appId"] as String, + secret = map["secret"] as String, + hint = map["hint"] as String, + createdById = map["createdById"] as String, + createdByName = map["createdByName"] as String, + lastAccessedAt = map["lastAccessedAt"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppSecretList.kt b/library/src/main/java/io/appwrite/models/AppSecretList.kt new file mode 100644 index 00000000..04d2ebb7 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppSecretList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App secrets list + */ +data class AppSecretList( + /** + * Total number of secrets that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of secrets. + */ + @SerializedName("secrets") + val secrets: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "secrets" to secrets.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppSecretList( + total = (map["total"] as Number).toLong(), + secrets = (map["secrets"] as List>).map { AppSecret.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt b/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt new file mode 100644 index 00000000..098b7f19 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt @@ -0,0 +1,93 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppSecretPlaintext + */ +data class AppSecretPlaintext( + /** + * Secret ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Secret creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Secret update time in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application ID this secret belongs to. + */ + @SerializedName("appId") + val appId: String, + + /** + * Application client secret. Returned only when the secret is created; subsequent reads always return an empty value. + */ + @SerializedName("secret") + val secret: String, + + /** + * Last few characters of the client secret, used to help identify it. + */ + @SerializedName("hint") + val hint: String, + + /** + * ID of the user who created the secret. + */ + @SerializedName("createdById") + val createdById: String, + + /** + * Name of the user who created the secret. + */ + @SerializedName("createdByName") + val createdByName: String, + + /** + * Time the secret was last used for authentication in ISO 8601 format. Null if never used. + */ + @SerializedName("lastAccessedAt") + var lastAccessedAt: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "appId" to appId as Any, + "secret" to secret as Any, + "hint" to hint as Any, + "createdById" to createdById as Any, + "createdByName" to createdByName as Any, + "lastAccessedAt" to lastAccessedAt as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppSecretPlaintext( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + appId = map["appId"] as String, + secret = map["secret"] as String, + hint = map["hint"] as String, + createdById = map["createdById"] as String, + createdByName = map["createdByName"] as String, + lastAccessedAt = map["lastAccessedAt"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppsList.kt b/library/src/main/java/io/appwrite/models/AppsList.kt new file mode 100644 index 00000000..5dcff821 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppsList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Apps list + */ +data class AppsList( + /** + * Total number of apps that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of apps. + */ + @SerializedName("apps") + val apps: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "apps" to apps.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppsList( + total = (map["total"] as Number).toLong(), + apps = (map["apps"] as List>).map { App.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/InputFile.kt b/library/src/main/java/io/appwrite/models/InputFile.kt index a5b377aa..779da916 100644 --- a/library/src/main/java/io/appwrite/models/InputFile.kt +++ b/library/src/main/java/io/appwrite/models/InputFile.kt @@ -2,8 +2,6 @@ package io.appwrite.models import java.io.File import java.net.URLConnection -import java.nio.file.Files -import java.nio.file.Paths class InputFile private constructor() { @@ -17,9 +15,7 @@ class InputFile private constructor() { fun fromFile(file: File) = InputFile().apply { path = file.canonicalPath filename = file.name - mimeType = Files.probeContentType(Paths.get(file.canonicalPath)) - ?: URLConnection.guessContentTypeFromName(filename) - ?: "" + mimeType = URLConnection.guessContentTypeFromName(filename) ?: "" sourceType = "file" } diff --git a/library/src/main/java/io/appwrite/models/Oauth2Approve.kt b/library/src/main/java/io/appwrite/models/Oauth2Approve.kt new file mode 100644 index 00000000..e09ab9ea --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Approve.kt @@ -0,0 +1,29 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Approve + */ +data class Oauth2Approve( + /** + * URL the end user should be redirected to after the grant is approved, carrying the authorization `code` and/or `id_token` along with the original `state`. + */ + @SerializedName("redirectUrl") + val redirectUrl: String, + +) { + fun toMap(): Map = mapOf( + "redirectUrl" to redirectUrl as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Approve( + redirectUrl = map["redirectUrl"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt b/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt new file mode 100644 index 00000000..e0812f7e --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Authorize + */ +data class Oauth2Authorize( + /** + * OAuth2 grant ID. Set when the user must give explicit consent; pass it to the approve or reject endpoint. Empty when a redirect URL is returned instead. + */ + @SerializedName("grantId") + val grantId: String, + + /** + * URL the end user should be redirected to when the flow can complete without consent. Empty when consent is still required. + */ + @SerializedName("redirectUrl") + val redirectUrl: String, + +) { + fun toMap(): Map = mapOf( + "grantId" to grantId as Any, + "redirectUrl" to redirectUrl as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Authorize( + grantId = map["grantId"] as String, + redirectUrl = map["redirectUrl"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt b/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt new file mode 100644 index 00000000..58c7e5ee --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt @@ -0,0 +1,69 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Device Authorization + */ +data class Oauth2DeviceAuthorization( + /** + * Device verification code used by the client to poll the token endpoint. + */ + @SerializedName("device_code") + val device_code: String, + + /** + * Short code the end user enters on the verification page. + */ + @SerializedName("user_code") + val user_code: String, + + /** + * URL where the end user enters the user code. + */ + @SerializedName("verification_uri") + val verification_uri: String, + + /** + * Verification URL with the user code prefilled as a query parameter. + */ + @SerializedName("verification_uri_complete") + val verification_uri_complete: String, + + /** + * Lifetime of the device code and user code in seconds. + */ + @SerializedName("expires_in") + val expires_in: Long, + + /** + * Minimum polling interval for the token endpoint in seconds. + */ + @SerializedName("interval") + val interval: Long, + +) { + fun toMap(): Map = mapOf( + "device_code" to device_code as Any, + "user_code" to user_code as Any, + "verification_uri" to verification_uri as Any, + "verification_uri_complete" to verification_uri_complete as Any, + "expires_in" to expires_in as Any, + "interval" to interval as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2DeviceAuthorization( + device_code = map["device_code"] as String, + user_code = map["user_code"] as String, + verification_uri = map["verification_uri"] as String, + verification_uri_complete = map["verification_uri_complete"] as String, + expires_in = (map["expires_in"] as Number).toLong(), + interval = (map["interval"] as Number).toLong(), + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Grant.kt b/library/src/main/java/io/appwrite/models/Oauth2Grant.kt new file mode 100644 index 00000000..ef1a7c39 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Grant.kt @@ -0,0 +1,117 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Grant + */ +data class Oauth2Grant( + /** + * Grant ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Grant creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Grant update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * ID of the user the grant belongs to. + */ + @SerializedName("userId") + val userId: String, + + /** + * ID of the OAuth2 client (app) the grant was requested for. + */ + @SerializedName("appId") + val appId: String, + + /** + * Requested OAuth2 scopes the user is being asked to consent to. + */ + @SerializedName("scopes") + val scopes: List, + + /** + * Requested RFC 8707 resource indicators the user is being asked to consent to. + */ + @SerializedName("resources") + val resources: List, + + /** + * Requested authorization_details the user is being asked to consent to, as a JSON string. Each entry has a `type` plus project-defined fields. + */ + @SerializedName("authorizationDetails") + val authorizationDetails: String, + + /** + * OIDC prompt directive the consent screen should honor. Space-separated list of: login, consent, select_account. + */ + @SerializedName("prompt") + val prompt: String, + + /** + * Redirect URI the user will be sent to after the flow completes. + */ + @SerializedName("redirectUri") + val redirectUri: String, + + /** + * Unix timestamp of when the user last authenticated. + */ + @SerializedName("authTime") + val authTime: Long, + + /** + * Grant expiration time in ISO 8601 format. + */ + @SerializedName("expire") + val expire: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "userId" to userId as Any, + "appId" to appId as Any, + "scopes" to scopes as Any, + "resources" to resources as Any, + "authorizationDetails" to authorizationDetails as Any, + "prompt" to prompt as Any, + "redirectUri" to redirectUri as Any, + "authTime" to authTime as Any, + "expire" to expire as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Grant( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + userId = map["userId"] as String, + appId = map["appId"] as String, + scopes = map["scopes"] as List, + resources = map["resources"] as List, + authorizationDetails = map["authorizationDetails"] as String, + prompt = map["prompt"] as String, + redirectUri = map["redirectUri"] as String, + authTime = (map["authTime"] as Number).toLong(), + expire = map["expire"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Organization.kt b/library/src/main/java/io/appwrite/models/Oauth2Organization.kt new file mode 100644 index 00000000..605916a0 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Organization.kt @@ -0,0 +1,29 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Organization + */ +data class Oauth2Organization( + /** + * Organization ID. + */ + @SerializedName("\$id") + val id: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Organization( + id = map["\$id"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt b/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt new file mode 100644 index 00000000..b90ee636 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 accessible organizations list + */ +data class Oauth2OrganizationList( + /** + * Total number of organizations that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of organizations. + */ + @SerializedName("organizations") + val organizations: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "organizations" to organizations.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2OrganizationList( + total = (map["total"] as Number).toLong(), + organizations = (map["organizations"] as List>).map { Oauth2Organization.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2PAR.kt b/library/src/main/java/io/appwrite/models/Oauth2PAR.kt new file mode 100644 index 00000000..94d175ee --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2PAR.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 PAR + */ +data class Oauth2PAR( + /** + * Authorization request handle to pass to the authorize endpoint. + */ + @SerializedName("request_uri") + val request_uri: String, + + /** + * Lifetime of the authorization request handle in seconds. + */ + @SerializedName("expires_in") + val expires_in: Long, + +) { + fun toMap(): Map = mapOf( + "request_uri" to request_uri as Any, + "expires_in" to expires_in as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2PAR( + request_uri = map["request_uri"] as String, + expires_in = (map["expires_in"] as Number).toLong(), + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Project.kt b/library/src/main/java/io/appwrite/models/Oauth2Project.kt new file mode 100644 index 00000000..eb085dce --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Project.kt @@ -0,0 +1,45 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Project + */ +data class Oauth2Project( + /** + * Project ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Region ID the project is deployed in. + */ + @SerializedName("region") + val region: String, + + /** + * API endpoint of the region the project is deployed in. Empty when the region has no public hostname configured. + */ + @SerializedName("endpoint") + val endpoint: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "region" to region as Any, + "endpoint" to endpoint as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Project( + id = map["\$id"] as String, + region = map["region"] as String, + endpoint = map["endpoint"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt b/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt new file mode 100644 index 00000000..551b9ee4 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 accessible projects list + */ +data class Oauth2ProjectList( + /** + * Total number of projects that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of projects. + */ + @SerializedName("projects") + val projects: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "projects" to projects.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2ProjectList( + total = (map["total"] as Number).toLong(), + projects = (map["projects"] as List>).map { Oauth2Project.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Reject.kt b/library/src/main/java/io/appwrite/models/Oauth2Reject.kt new file mode 100644 index 00000000..b23f308c --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Reject.kt @@ -0,0 +1,29 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Reject + */ +data class Oauth2Reject( + /** + * URL the end user should be redirected to after the grant is rejected, carrying an `access_denied` error. + */ + @SerializedName("redirectUrl") + val redirectUrl: String, + +) { + fun toMap(): Map = mapOf( + "redirectUrl" to redirectUrl as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Reject( + redirectUrl = map["redirectUrl"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Token.kt b/library/src/main/java/io/appwrite/models/Oauth2Token.kt new file mode 100644 index 00000000..f36f7768 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Token.kt @@ -0,0 +1,77 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Token + */ +data class Oauth2Token( + /** + * OAuth2 access token. + */ + @SerializedName("access_token") + val access_token: String, + + /** + * OAuth2 token type. + */ + @SerializedName("token_type") + val token_type: String, + + /** + * Access token lifetime in seconds. + */ + @SerializedName("expires_in") + val expires_in: Long, + + /** + * OAuth2 refresh token. + */ + @SerializedName("refresh_token") + val refresh_token: String, + + /** + * Space-separated scopes granted to the access token. + */ + @SerializedName("scope") + val scope: String, + + /** + * Granted RFC 9396 authorization details as a JSON string. + */ + @SerializedName("authorization_details") + var authorization_details: String?, + + /** + * OpenID Connect ID token. Returned when the `openid` scope is granted. + */ + @SerializedName("id_token") + var id_token: String?, + +) { + fun toMap(): Map = mapOf( + "access_token" to access_token as Any, + "token_type" to token_type as Any, + "expires_in" to expires_in as Any, + "refresh_token" to refresh_token as Any, + "scope" to scope as Any, + "authorization_details" to authorization_details as Any?, + "id_token" to id_token as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Token( + access_token = map["access_token"] as String, + token_type = map["token_type"] as String, + expires_in = (map["expires_in"] as Number).toLong(), + refresh_token = map["refresh_token"] as String, + scope = map["scope"] as String, + authorization_details = map["authorization_details"] as? String, + id_token = map["id_token"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/RealtimeModels.kt b/library/src/main/java/io/appwrite/models/RealtimeModels.kt index ed55bbb1..8d083ad0 100644 --- a/library/src/main/java/io/appwrite/models/RealtimeModels.kt +++ b/library/src/main/java/io/appwrite/models/RealtimeModels.kt @@ -39,14 +39,33 @@ data class RealtimeCallback( open class RealtimeResponse( val type: String, val data: Any? -) +) { + companion object { + fun from(map: Map) = RealtimeResponse( + type = map["type"] as? String ?: "", + data = map["data"], + ) + } +} data class RealtimeResponseEvent( val events: Collection, val channels: Collection, val timestamp: String, var payload: T -) +) { + companion object { + fun from(map: Map): RealtimeResponseEvent? { + val payload = map["payload"] ?: return null + return RealtimeResponseEvent( + events = (map["events"] as? List<*>)?.map { it.toString() } ?: emptyList(), + channels = (map["channels"] as? List<*>)?.map { it.toString() } ?: emptyList(), + timestamp = map["timestamp"]?.toString() ?: "", + payload = payload, + ) + } + } +} enum class RealtimeCode(val value: Int) { POLICY_VIOLATION(1008), diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index 17cdf820..8df38221 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -14,7 +14,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Account service allows you to authenticate and manage a user account. */ + * The Account service allows you to authenticate and manage a user account. + */ class Account(client: Client) : Service(client) { /** * Get the currently logged in user. @@ -1548,11 +1549,17 @@ class Account(client: Client) : Service(client) { /** * Get the list of active sessions across different devices for the currently logged in user. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.SessionList] */ - suspend fun listSessions(): io.appwrite.models.SessionList { + @JvmOverloads + suspend fun listSessions( + total: Boolean? = null, + ): io.appwrite.models.SessionList { val apiPath = "/account/sessions" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -1572,13 +1579,19 @@ class Account(client: Client) : Service(client) { } /** - * Delete all sessions from the user account and remove any sessions cookies from the end client. + * Delete all sessions from the user account and remove any sessions cookies from the end client. Pass `current` as false to keep the session making the request and sign out of every other session. * + * @param current Delete the current session too. Use false to sign out of every other session while staying signed in on this one. * @return [Any] */ - suspend fun deleteSessions(): Any { + @JvmOverloads + suspend fun deleteSessions( + current: Boolean? = null, + ): Any { val apiPath = "/account/sessions" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "current" to current, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1621,22 +1634,26 @@ class Account(client: Client) : Service(client) { } /** - * Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. + * Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. Use the optional `duration` parameter to create a shorter session, for example when the user doesn't choose "remember me". It must be at least 60 seconds and cannot exceed the project maximum session length. * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * @param email User email. * @param password User password. Must be at least 8 chars. + * @param duration Session length in seconds. Minimum is 60 seconds, and it cannot exceed the project maximum session length. Defaults to the project maximum session length. * @return [io.appwrite.models.Session] */ + @JvmOverloads suspend fun createEmailPasswordSession( email: String, password: String, + duration: Long? = null, ): io.appwrite.models.Session { val apiPath = "/account/sessions/email" val apiParams = mutableMapOf( "email" to email, "password" to password, + "duration" to duration, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), @@ -1763,7 +1780,7 @@ class Account(client: Client) : Service(client) { * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, webflow, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -2247,7 +2264,7 @@ class Account(client: Client) : Service(client) { * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, webflow, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. diff --git a/library/src/main/java/io/appwrite/services/Apps.kt b/library/src/main/java/io/appwrite/services/Apps.kt new file mode 100644 index 00000000..20f0de13 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Apps.kt @@ -0,0 +1,881 @@ +package io.appwrite.services + +import android.net.Uri +import io.appwrite.Client +import io.appwrite.Service +import io.appwrite.exceptions.AppwriteException +import io.appwrite.extensions.classOf +import io.appwrite.models.* +import okhttp3.Cookie +import java.io.File + +/** + * The Apps service allows you to manage OAuth2 applications, their keys, secrets, scopes, and installations. + */ +class Apps(client: Client) : Service(client) { + /** + * List applications. + * + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppsList] + */ + @JvmOverloads + suspend fun list( + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppsList { + val apiPath = "/apps" + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppsList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppsList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppsList::class.java, + converter, + ) + } + + /** + * Create a new application. + * + * @param appId Application ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + * @param name Application name. + * @param redirectUris Redirect URIs. Each must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI (e.g. com.example.app:/oauth), and must not contain a fragment. + * @param description Application description shown to users during OAuth2 consent. + * @param clientUri Application homepage URL shown to users during OAuth2 consent. + * @param logoUri Application logo URL shown to users during OAuth2 consent. + * @param privacyPolicyUrl Application privacy policy URL shown to users during OAuth2 consent. + * @param termsUrl Application terms of service URL shown to users during OAuth2 consent. + * @param contacts Application support or security contact emails. Maximum of 100 contacts are allowed. + * @param tagline Application tagline shown to users during OAuth2 consent. + * @param tags Application tags shown to users during OAuth2 consent. Maximum of 100 tags are allowed, each up to 64 characters long. + * @param images Application image URLs shown to users during OAuth2 consent. Maximum of 100 images are allowed. + * @param supportUrl Application support URL shown to users during OAuth2 consent. + * @param dataDeletionUrl Application data deletion URL shown to users during OAuth2 consent. + * @param postLogoutRedirectUris Post-logout redirect URIs for OpenID Connect RP-Initiated Logout. Each must be an https URL, an http loopback URL, or a private-use scheme URI, and must not contain a fragment. After ending the user session, the logout endpoint only redirects to URIs in this list. + * @param enabled Is application enabled? + * @param type OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. + * @param deviceFlow Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. + * @param teamId Team unique ID. + * @return [io.appwrite.models.App] + */ + @JvmOverloads + suspend fun create( + appId: String, + name: String, + redirectUris: List, + description: String? = null, + clientUri: String? = null, + logoUri: String? = null, + privacyPolicyUrl: String? = null, + termsUrl: String? = null, + contacts: List? = null, + tagline: String? = null, + tags: List? = null, + images: List? = null, + supportUrl: String? = null, + dataDeletionUrl: String? = null, + postLogoutRedirectUris: List? = null, + enabled: Boolean? = null, + type: String? = null, + deviceFlow: Boolean? = null, + teamId: String? = null, + ): io.appwrite.models.App { + val apiPath = "/apps" + val apiParams = mutableMapOf( + "appId" to appId, + "name" to name, + "description" to description, + "clientUri" to clientUri, + "logoUri" to logoUri, + "privacyPolicyUrl" to privacyPolicyUrl, + "termsUrl" to termsUrl, + "contacts" to contacts, + "tagline" to tagline, + "tags" to tags, + "images" to images, + "supportUrl" to supportUrl, + "dataDeletionUrl" to dataDeletionUrl, + "redirectUris" to redirectUris, + "postLogoutRedirectUris" to postLogoutRedirectUris, + "enabled" to enabled, + "type" to type, + "deviceFlow" to deviceFlow, + "teamId" to teamId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * List scopes an application can request when installed on a team. + * + * @return [io.appwrite.models.AppScopeList] + */ + suspend fun listInstallationScopes(): io.appwrite.models.AppScopeList { + val apiPath = "/apps/scopes/installations" + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppScopeList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppScopeList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppScopeList::class.java, + converter, + ) + } + + /** + * List scopes an application can request during the OAuth2 flow. + * + * @return [io.appwrite.models.AppScopeList] + */ + suspend fun listOAuth2Scopes(): io.appwrite.models.AppScopeList { + val apiPath = "/apps/scopes/oauth2" + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppScopeList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppScopeList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppScopeList::class.java, + converter, + ) + } + + /** + * Get an application by its unique ID. + * + * @param appId Application unique ID. + * @return [io.appwrite.models.App] + */ + suspend fun get( + appId: String, + ): io.appwrite.models.App { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * Update an application by its unique ID. + * + * @param appId Application unique ID. + * @param name Application name. + * @param description Application description shown to users during OAuth2 consent. + * @param clientUri Application homepage URL shown to users during OAuth2 consent. + * @param logoUri Application logo URL shown to users during OAuth2 consent. + * @param privacyPolicyUrl Application privacy policy URL shown to users during OAuth2 consent. + * @param termsUrl Application terms of service URL shown to users during OAuth2 consent. + * @param contacts Application support or security contact emails. Maximum of 100 contacts are allowed. + * @param tagline Application tagline shown to users during OAuth2 consent. + * @param tags Application tags shown to users during OAuth2 consent. Maximum of 100 tags are allowed, each up to 64 characters long. + * @param images Application image URLs shown to users during OAuth2 consent. Maximum of 100 images are allowed. + * @param supportUrl Application support URL shown to users during OAuth2 consent. + * @param dataDeletionUrl Application data deletion URL shown to users during OAuth2 consent. + * @param enabled Is application enabled? + * @param redirectUris Redirect URIs. Each must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI (e.g. com.example.app:/oauth), and must not contain a fragment. + * @param postLogoutRedirectUris Post-logout redirect URIs for OpenID Connect RP-Initiated Logout. Each must be an https URL, an http loopback URL, or a private-use scheme URI, and must not contain a fragment. After ending the user session, the logout endpoint only redirects to URIs in this list. + * @param type OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. + * @param deviceFlow Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. + * @param installationScopes Scopes the application requests when installed on a team. Only scopes allowed by the project's OAuth2 server installation scopes configuration are accepted; use the list installation scopes endpoint to discover available values. Maximum of 100 scopes are allowed. + * @param installationRedirectUrl URL users are redirected to after creating or updating an installation of this application. Must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI, and must not contain a fragment. Leave empty for no redirect. + * @return [io.appwrite.models.App] + */ + @JvmOverloads + suspend fun update( + appId: String, + name: String, + description: String? = null, + clientUri: String? = null, + logoUri: String? = null, + privacyPolicyUrl: String? = null, + termsUrl: String? = null, + contacts: List? = null, + tagline: String? = null, + tags: List? = null, + images: List? = null, + supportUrl: String? = null, + dataDeletionUrl: String? = null, + enabled: Boolean? = null, + redirectUris: List? = null, + postLogoutRedirectUris: List? = null, + type: String? = null, + deviceFlow: Boolean? = null, + installationScopes: List? = null, + installationRedirectUrl: String? = null, + ): io.appwrite.models.App { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "name" to name, + "description" to description, + "clientUri" to clientUri, + "logoUri" to logoUri, + "privacyPolicyUrl" to privacyPolicyUrl, + "termsUrl" to termsUrl, + "contacts" to contacts, + "tagline" to tagline, + "tags" to tags, + "images" to images, + "supportUrl" to supportUrl, + "dataDeletionUrl" to dataDeletionUrl, + "enabled" to enabled, + "redirectUris" to redirectUris, + "postLogoutRedirectUris" to postLogoutRedirectUris, + "type" to type, + "deviceFlow" to deviceFlow, + "installationScopes" to installationScopes, + "installationRedirectUrl" to installationRedirectUrl, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * Delete an application by its unique ID. + * + * @param appId Application unique ID. + * @return [Any] + */ + suspend fun delete( + appId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. + * + * @param appId Application unique ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppInstallationList] + */ + @JvmOverloads + suspend fun listInstallations( + appId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppInstallationList { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/installations" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallationList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallationList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallationList::class.java, + converter, + ) + } + + /** + * Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. + * + * @param appId Application unique ID. + * @param installationId Installation unique ID. + * @return [io.appwrite.models.AppInstallation] + */ + suspend fun getInstallation( + appId: String, + installationId: String, + ): io.appwrite.models.AppInstallation { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } + val apiPath = ("/apps/{appId}/installations/{installationId}" + .replace("{appId}", appId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, + ) + } + + /** + * Delete an installation of an application by its unique ID. Requires a caller with update access to the app. Previously issued installation access tokens are revoked. + * + * @param appId Application unique ID. + * @param installationId Installation unique ID. + * @return [Any] + */ + suspend fun deleteInstallation( + appId: String, + installationId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } + val apiPath = ("/apps/{appId}/installations/{installationId}" + .replace("{appId}", appId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. + * + * @param appId Application unique ID. + * @param installationId Installation unique ID. + * @return [io.appwrite.models.Oauth2Token] + */ + suspend fun createInstallationToken( + appId: String, + installationId: String, + ): io.appwrite.models.Oauth2Token { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } + val apiPath = ("/apps/{appId}/installations/{installationId}/tokens" + .replace("{appId}", appId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Token.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Token::class.java, + converter, + ) + } + + /** + * List app keys for an application. + * + * @param appId Application unique ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppKeyList] + */ + @JvmOverloads + suspend fun listKeys( + appId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppKeyList { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/keys" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppKeyList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppKeyList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppKeyList::class.java, + converter, + ) + } + + /** + * Create a new app key for an application. App keys carry no scopes; send one in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header to list the application's installations and create installation access tokens. + * + * @param appId Application unique ID. + * @return [io.appwrite.models.AppKey] + */ + suspend fun createKey( + appId: String, + ): io.appwrite.models.AppKey { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/keys" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppKey = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppKey.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppKey::class.java, + converter, + ) + } + + /** + * Get an app key by its unique ID. + * + * @param appId Application unique ID. + * @param keyId App key unique ID. + * @return [io.appwrite.models.AppKey] + */ + suspend fun getKey( + appId: String, + keyId: String, + ): io.appwrite.models.AppKey { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (keyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"keyId\"") + } + val apiPath = ("/apps/{appId}/keys/{keyId}" + .replace("{appId}", appId) + .replace("{keyId}", keyId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppKey = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppKey.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppKey::class.java, + converter, + ) + } + + /** + * Delete an app key by its unique ID. + * + * @param appId Application unique ID. + * @param keyId App key unique ID. + * @return [Any] + */ + suspend fun deleteKey( + appId: String, + keyId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (keyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"keyId\"") + } + val apiPath = ("/apps/{appId}/keys/{keyId}" + .replace("{appId}", appId) + .replace("{keyId}", keyId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * List client secrets for an application. + * + * @param appId Application unique ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppSecretList] + */ + @JvmOverloads + suspend fun listSecrets( + appId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppSecretList { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/secrets" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppSecretList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppSecretList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppSecretList::class.java, + converter, + ) + } + + /** + * Create a new client secret for an application. + * + * @param appId Application unique ID. + * @return [io.appwrite.models.AppSecretPlaintext] + */ + suspend fun createSecret( + appId: String, + ): io.appwrite.models.AppSecretPlaintext { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/secrets" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppSecretPlaintext = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppSecretPlaintext.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppSecretPlaintext::class.java, + converter, + ) + } + + /** + * Get an application client secret by its unique ID. + * + * @param appId Application unique ID. + * @param secretId Secret unique ID. + * @return [io.appwrite.models.AppSecret] + */ + suspend fun getSecret( + appId: String, + secretId: String, + ): io.appwrite.models.AppSecret { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (secretId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"secretId\"") + } + val apiPath = ("/apps/{appId}/secrets/{secretId}" + .replace("{appId}", appId) + .replace("{secretId}", secretId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppSecret = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppSecret.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppSecret::class.java, + converter, + ) + } + + /** + * Delete an application client secret by its unique ID. + * + * @param appId Application unique ID. + * @param secretId Secret unique ID. + * @return [Any] + */ + suspend fun deleteSecret( + appId: String, + secretId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (secretId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"secretId\"") + } + val apiPath = ("/apps/{appId}/secrets/{secretId}" + .replace("{appId}", appId) + .replace("{secretId}", secretId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Transfer an application to another team by its unique ID. + * + * @param appId Application unique ID. + * @param teamId Team ID of the team to transfer application to. + * @return [io.appwrite.models.App] + */ + suspend fun updateTeam( + appId: String, + teamId: String, + ): io.appwrite.models.App { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/team" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "teamId" to teamId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * Revoke all tokens for an application by its unique ID. + * + * @param appId Application unique ID. + * @return [Any] + */ + suspend fun deleteTokens( + appId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/tokens" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } +} diff --git a/library/src/main/java/io/appwrite/services/Avatars.kt b/library/src/main/java/io/appwrite/services/Avatars.kt index cce2d3da..3b11d165 100644 --- a/library/src/main/java/io/appwrite/services/Avatars.kt +++ b/library/src/main/java/io/appwrite/services/Avatars.kt @@ -12,7 +12,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars. */ + * The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars. + */ class Avatars(client: Client) : Service(client) { /** * You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET /account/sessions](https://appwrite.io/docs/references/cloud/client-web/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings. @@ -240,7 +241,7 @@ class Avatars(client: Client) : Service(client) { } /** - * Returns the best available profile photo for a user. The endpoint tries each source in priority order and returns the first successful result: OAuth2 identity photo, Gravatar, Libravatar, Appwrite Initials, built-in static fallback. + * Returns the best available profile photo for a user. The endpoint tries each source in priority order and returns the first successful result: a custom uploaded photo (see avatars.updatePhoto), OAuth2 identity photo, Gravatar, Libravatar, Appwrite Initials, built-in static fallback. * * Passing `userId` — `current()` for the authenticated user — resolves the photo from everything known about that user: identity photos, email, and name. An explicit `emailHash` or `name` then overrides just that value, and the user's remaining sources stay in the chain. Without `userId`, passing `emailHash` and/or `name` resolves the avatar from those values alone: the hash is looked up on Gravatar and Libravatar, the name is rendered as initials, and the session user stays out of the chain so their own photo never shadows the avatar being asked for. When nothing is passed, the photo resolves for the currently authenticated user. Emails are only ever accepted pre-hashed, so no address ends up in a URL. * @@ -289,6 +290,82 @@ class Avatars(client: Client) : Service(client) { ) } + /** + * Update the profile photo of the currently authenticated user. The uploaded image takes priority over every other photo source, including OAuth2 identity photos, Gravatar, and Libravatar. Updating an already customized photo replaces it. The image must be at most 5MB and is sent in a single request. + * + * @param file Binary image file of at most 5MB. Allowed file types are png, jpg, jpeg, and webp. + * @return [io.appwrite.models.Account] + */ + suspend fun updatePhoto( + file: InputFile, + nestedType: Class, + onProgress: ((UploadProgress) -> Unit)? = null + ): io.appwrite.models.Account { + val apiPath = "/avatars/photo" + val apiParams = mutableMapOf( + "file" to file, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "multipart/form-data", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Account = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Account.from(map = it as Map, nestedType) + } + val idParamName: String? = null + val paramName = "file" + return client.chunkedUpload( + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + paramName = paramName, + idParamName = idParamName, + onProgress = onProgress, + ) + } + + /** + * Update the profile photo of the currently authenticated user. The uploaded image takes priority over every other photo source, including OAuth2 identity photos, Gravatar, and Libravatar. Updating an already customized photo replaces it. The image must be at most 5MB and is sent in a single request. + * + * @param file Binary image file of at most 5MB. Allowed file types are png, jpg, jpeg, and webp. + * @return [io.appwrite.models.Account] + */ + @Throws(AppwriteException::class) + suspend fun updatePhoto( + file: InputFile, + onProgress: ((UploadProgress) -> Unit)? = null + ): io.appwrite.models.Account> = updatePhoto( + file, + nestedType = classOf(), + onProgress = onProgress + ) + + /** + * Delete the profile photo of the currently authenticated user and store the built-in static placeholder in its place. The placeholder is the user's photo from then on, so it takes priority over every other photo source — OAuth2 identity photos, Gravatar, Libravatar, and initials — until a new photo is uploaded with avatars.updatePhoto. + * + * @return [Any] + */ + suspend fun deletePhoto(): Any { + val apiPath = "/avatars/photo" + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + /** * Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image. * @@ -334,7 +411,7 @@ class Avatars(client: Client) : Service(client) { * When width and height are specified, the image is resized accordingly. If both dimensions are 0, the API provides an image at original size. If dimensions are not specified, the default viewport size is 1280x720px. * * @param url Website URL which you want to capture. - * @param headers HTTP headers to send with the browser request. Defaults to empty. + * @param headers HTTP headers to send with the browser request. Only Accept and Accept-Language are allowed. Defaults to empty. * @param viewportWidth Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280. * @param viewportHeight Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720. * @param scale Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1. diff --git a/library/src/main/java/io/appwrite/services/Databases.kt b/library/src/main/java/io/appwrite/services/Databases.kt index fcc85678..8b3b8215 100644 --- a/library/src/main/java/io/appwrite/services/Databases.kt +++ b/library/src/main/java/io/appwrite/services/Databases.kt @@ -10,12 +10,14 @@ import okhttp3.Cookie import java.io.File /** - * The Databases service allows you to create structured collections of documents, query and filter lists of documents */ + * The Databases service allows you to create structured collections of documents, query and filter lists of documents + */ class Databases(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @Deprecated( @@ -25,10 +27,12 @@ class Databases(client: Client) : Service(client) { @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/databases/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/main/java/io/appwrite/services/DocumentsDB.kt b/library/src/main/java/io/appwrite/services/DocumentsDB.kt index fb7cd720..9c403245 100644 --- a/library/src/main/java/io/appwrite/services/DocumentsDB.kt +++ b/library/src/main/java/io/appwrite/services/DocumentsDB.kt @@ -10,21 +10,25 @@ import okhttp3.Cookie import java.io.File /** - * */ + * + */ class DocumentsDB(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/documentsdb/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/main/java/io/appwrite/services/Functions.kt b/library/src/main/java/io/appwrite/services/Functions.kt index 57821693..0417207c 100644 --- a/library/src/main/java/io/appwrite/services/Functions.kt +++ b/library/src/main/java/io/appwrite/services/Functions.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Functions Service allows you view, create and manage your Cloud Functions. */ + * The Functions Service allows you view, create and manage your Cloud Functions. + */ class Functions(client: Client) : Service(client) { /** * Get a list of all the current user function execution logs. You can use the query params to filter your results. diff --git a/library/src/main/java/io/appwrite/services/Graphql.kt b/library/src/main/java/io/appwrite/services/Graphql.kt index c1a776d9..dba5986f 100644 --- a/library/src/main/java/io/appwrite/services/Graphql.kt +++ b/library/src/main/java/io/appwrite/services/Graphql.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The GraphQL API allows you to query and mutate your Appwrite server using GraphQL. */ + * The GraphQL API allows you to query and mutate your Appwrite server using GraphQL. + */ class Graphql(client: Client) : Service(client) { /** * Execute a GraphQL mutation. diff --git a/library/src/main/java/io/appwrite/services/Locale.kt b/library/src/main/java/io/appwrite/services/Locale.kt index 9c08a68d..a6eb364c 100644 --- a/library/src/main/java/io/appwrite/services/Locale.kt +++ b/library/src/main/java/io/appwrite/services/Locale.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Locale service allows you to customize your app based on your users' location. */ + * The Locale service allows you to customize your app based on your users' location. + */ class Locale(client: Client) : Service(client) { /** * Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language. @@ -43,11 +44,17 @@ class Locale(client: Client) : Service(client) { /** * List of all locale codes in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.LocaleCodeList] */ - suspend fun listCodes(): io.appwrite.models.LocaleCodeList { + @JvmOverloads + suspend fun listCodes( + total: Boolean? = null, + ): io.appwrite.models.LocaleCodeList { val apiPath = "/locale/codes" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -69,11 +76,17 @@ class Locale(client: Client) : Service(client) { /** * List of all continents. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.ContinentList] */ - suspend fun listContinents(): io.appwrite.models.ContinentList { + @JvmOverloads + suspend fun listContinents( + total: Boolean? = null, + ): io.appwrite.models.ContinentList { val apiPath = "/locale/continents" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -95,11 +108,17 @@ class Locale(client: Client) : Service(client) { /** * List of all countries. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.CountryList] */ - suspend fun listCountries(): io.appwrite.models.CountryList { + @JvmOverloads + suspend fun listCountries( + total: Boolean? = null, + ): io.appwrite.models.CountryList { val apiPath = "/locale/countries" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -121,11 +140,17 @@ class Locale(client: Client) : Service(client) { /** * List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.CountryList] */ - suspend fun listCountriesEU(): io.appwrite.models.CountryList { + @JvmOverloads + suspend fun listCountriesEU( + total: Boolean? = null, + ): io.appwrite.models.CountryList { val apiPath = "/locale/countries/eu" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -147,11 +172,17 @@ class Locale(client: Client) : Service(client) { /** * List of all countries phone codes. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.PhoneList] */ - suspend fun listCountriesPhones(): io.appwrite.models.PhoneList { + @JvmOverloads + suspend fun listCountriesPhones( + total: Boolean? = null, + ): io.appwrite.models.PhoneList { val apiPath = "/locale/countries/phones" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -173,11 +204,17 @@ class Locale(client: Client) : Service(client) { /** * List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.CurrencyList] */ - suspend fun listCurrencies(): io.appwrite.models.CurrencyList { + @JvmOverloads + suspend fun listCurrencies( + total: Boolean? = null, + ): io.appwrite.models.CurrencyList { val apiPath = "/locale/currencies" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -199,11 +236,17 @@ class Locale(client: Client) : Service(client) { /** * List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.LanguageList] */ - suspend fun listLanguages(): io.appwrite.models.LanguageList { + @JvmOverloads + suspend fun listLanguages( + total: Boolean? = null, + ): io.appwrite.models.LanguageList { val apiPath = "/locale/languages" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", diff --git a/library/src/main/java/io/appwrite/services/Messaging.kt b/library/src/main/java/io/appwrite/services/Messaging.kt index 0e816f78..e0e36b13 100644 --- a/library/src/main/java/io/appwrite/services/Messaging.kt +++ b/library/src/main/java/io/appwrite/services/Messaging.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.). */ + * The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.). + */ class Messaging(client: Client) : Service(client) { /** * Create a new subscriber. diff --git a/library/src/main/java/io/appwrite/services/Oauth2.kt b/library/src/main/java/io/appwrite/services/Oauth2.kt new file mode 100644 index 00000000..030f4cef --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Oauth2.kt @@ -0,0 +1,680 @@ +package io.appwrite.services + +import android.net.Uri +import io.appwrite.Client +import io.appwrite.Service +import io.appwrite.exceptions.AppwriteException +import io.appwrite.extensions.classOf +import io.appwrite.models.* +import okhttp3.Cookie +import java.io.File + +/** + * The OAuth2 service allows you to authorize apps and issue standards-based OAuth2 and OpenID Connect tokens. + */ +class Oauth2(client: Client) : Service(client) { + /** + * Approve an OAuth2 grant after the user gives consent. Returns the `redirectUrl` the end user should be sent to. The consent screen may optionally pass enriched `authorization_details` to record the concrete resources the user selected. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param grantId Grant ID made during authorization, provided to consent screen in URL search params. + * @param authorizationDetails Enriched `authorization_details` the user consented to, replacing what the client requested. Each entry must use a `type` the project accepts. Optional; omit to keep the originally requested details. + * @param scope Space-separated scopes the user consented to. Must be a subset of the scopes originally requested; identity scopes such as `openid` are always retained. Optional; omit to keep the originally requested scopes. + * @return [io.appwrite.models.Oauth2Approve] + */ + @JvmOverloads + suspend fun approve( + grantId: String, + authorizationDetails: String? = null, + scope: String? = null, + ): io.appwrite.models.Oauth2Approve { + val apiPath = ("/oauth2/{project_id}/approve" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "grant_id" to grantId, + "authorization_details" to authorizationDetails, + "scope" to scope, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Approve = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Approve.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Approve::class.java, + converter, + ) + } + + /** + * Begin the OAuth2 authorization flow. When called without a session, the user is redirected to the consent screen without grant ID. When called with a session, the redirect URL includes param for grant ID. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param redirectUri Redirect URI where visitor will be redirected after authorization, whether successful or not. + * @param responseType OAuth2 / OIDC response type. One of `code` (Authorization Code Flow), `id_token` (Implicit Flow, OIDC login only), or `code id_token` (Hybrid Flow). + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + * @param state OAuth2 state. You receive this back in the redirect URI. + * @param nonce OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + * @param codeChallenge PKCE code challenge. Required when OAuth2 app is public. + * @param codeChallengeMethod PKCE code challenge method. Required when OAuth2 app is public. + * @param prompt OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + * @param maxAge OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @param requestUri OAuth2 authorization request handle returned by the pushed authorization request endpoint. + * @return [io.appwrite.models.Oauth2Authorize] + */ + @JvmOverloads + suspend fun authorize( + clientId: String? = null, + redirectUri: String? = null, + responseType: String? = null, + scope: String? = null, + state: String? = null, + nonce: String? = null, + codeChallenge: String? = null, + codeChallengeMethod: String? = null, + prompt: String? = null, + maxAge: Long? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + requestUri: String? = null, + ): io.appwrite.models.Oauth2Authorize { + val apiPath = ("/oauth2/{project_id}/authorize" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "redirect_uri" to redirectUri, + "response_type" to responseType, + "scope" to scope, + "state" to state, + "nonce" to nonce, + "code_challenge" to codeChallenge, + "code_challenge_method" to codeChallengeMethod, + "prompt" to prompt, + "max_age" to maxAge, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + "request_uri" to requestUri, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Authorize = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Authorize.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Authorize::class.java, + converter, + ) + } + + /** + * Begin the OAuth2 authorization flow. When called without a session, the user is redirected to the consent screen without grant ID. When called with a session, the redirect URL includes param for grant ID. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param redirectUri Redirect URI where visitor will be redirected after authorization, whether successful or not. + * @param responseType OAuth2 / OIDC response type. One of `code` (Authorization Code Flow), `id_token` (Implicit Flow, OIDC login only), or `code id_token` (Hybrid Flow). + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + * @param state OAuth2 state. You receive this back in the redirect URI. + * @param nonce OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + * @param codeChallenge PKCE code challenge. Required when OAuth2 app is public. + * @param codeChallengeMethod PKCE code challenge method. Required when OAuth2 app is public. + * @param prompt OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + * @param maxAge OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @param requestUri OAuth2 authorization request handle returned by the pushed authorization request endpoint. + * @return [io.appwrite.models.Oauth2Authorize] + */ + @JvmOverloads + suspend fun authorizePost( + clientId: String? = null, + redirectUri: String? = null, + responseType: String? = null, + scope: String? = null, + state: String? = null, + nonce: String? = null, + codeChallenge: String? = null, + codeChallengeMethod: String? = null, + prompt: String? = null, + maxAge: Long? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + requestUri: String? = null, + ): io.appwrite.models.Oauth2Authorize { + val apiPath = ("/oauth2/{project_id}/authorize" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "redirect_uri" to redirectUri, + "response_type" to responseType, + "scope" to scope, + "state" to state, + "nonce" to nonce, + "code_challenge" to codeChallenge, + "code_challenge_method" to codeChallengeMethod, + "prompt" to prompt, + "max_age" to maxAge, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + "request_uri" to requestUri, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Authorize = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Authorize.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Authorize::class.java, + converter, + ) + } + + /** + * Start the OAuth2 Device Authorization Grant. Returns the device code, user code, verification URL, expiration, and polling interval. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @return [io.appwrite.models.Oauth2DeviceAuthorization] + */ + @JvmOverloads + suspend fun createDeviceAuthorization( + clientId: String? = null, + scope: String? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + ): io.appwrite.models.Oauth2DeviceAuthorization { + val apiPath = ("/oauth2/{project_id}/device_authorization" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "scope" to scope, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2DeviceAuthorization = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2DeviceAuthorization.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2DeviceAuthorization::class.java, + converter, + ) + } + + /** + * Exchange a device flow user code for an OAuth2 grant. The authenticated user is bound to the pending grant. Pass the returned grant ID to the get grant endpoint to render the consent screen, then to the approve or reject endpoint to complete the flow. + * + * @param userCode User code displayed on the device. + * @return [io.appwrite.models.Oauth2Grant] + */ + suspend fun createGrant( + userCode: String, + ): io.appwrite.models.Oauth2Grant { + val apiPath = ("/oauth2/{project_id}/grants" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "user_code" to userCode, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Grant = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Grant.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Grant::class.java, + converter, + ) + } + + /** + * Get an OAuth2 grant by its ID. Used by the consent screen to display the details of the authorization the user is being asked to approve. A grant can only be read by the user it belongs to, or by server SDK. + * + * @param grantId Grant ID made during authorization, provided to consent screen in URL search params. + * @return [io.appwrite.models.Oauth2Grant] + */ + suspend fun getGrant( + grantId: String, + ): io.appwrite.models.Oauth2Grant { + if (grantId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"grantId\"") + } + val apiPath = ("/oauth2/{project_id}/grants/{grant_id}" + .replace("{project_id}", client.config["project"].orEmpty()) + .replace("{grant_id}", grantId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Grant = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Grant.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Grant::class.java, + converter, + ) + } + + /** + * OpenID Connect RP-Initiated Logout. Ends the user session and revokes the tokens issued to the app identified by the `id_token_hint`, then redirects the user to `post_logout_redirect_uri` when it matches a URI registered on the app. + * + * @param idTokenHint ID Token previously issued to the app, used as proof of the logout request. Required to end the session; signature and issuer are validated while expiry is ignored. + * @param logoutHint Hint about the user that is logging out. Accepted for OIDC compatibility. + * @param clientId OAuth2 client ID. When both `client_id` and `id_token_hint` are provided, they must identify the same app. + * @param postLogoutRedirectUri URI to redirect the user to after logout. Must exactly match a URI registered in the app's `postLogoutRedirectUris`. + * @param state Opaque value passed back unchanged in the `state` query param of the post-logout redirect. + * @param uiLocales Preferred languages for any logout UI, as space-separated BCP47 tags. Accepted for OIDC compatibility. + * @return [Any] + */ + @JvmOverloads + suspend fun logout( + idTokenHint: String? = null, + logoutHint: String? = null, + clientId: String? = null, + postLogoutRedirectUri: String? = null, + state: String? = null, + uiLocales: String? = null, + ): Any { + val apiPath = ("/oauth2/{project_id}/logout" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "id_token_hint" to idTokenHint, + "logout_hint" to logoutHint, + "client_id" to clientId, + "post_logout_redirect_uri" to postLogoutRedirectUri, + "state" to state, + "ui_locales" to uiLocales, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * OpenID Connect RP-Initiated Logout. Ends the user session and revokes the tokens issued to the app identified by the `id_token_hint`, then redirects the user to `post_logout_redirect_uri` when it matches a URI registered on the app. + * + * @param idTokenHint ID Token previously issued to the app, used as proof of the logout request. Required to end the session; signature and issuer are validated while expiry is ignored. + * @param logoutHint Hint about the user that is logging out. Accepted for OIDC compatibility. + * @param clientId OAuth2 client ID. When both `client_id` and `id_token_hint` are provided, they must identify the same app. + * @param postLogoutRedirectUri URI to redirect the user to after logout. Must exactly match a URI registered in the app's `postLogoutRedirectUris`. + * @param state Opaque value passed back unchanged in the `state` query param of the post-logout redirect. + * @param uiLocales Preferred languages for any logout UI, as space-separated BCP47 tags. Accepted for OIDC compatibility. + * @return [Any] + */ + @JvmOverloads + suspend fun logoutPost( + idTokenHint: String? = null, + logoutHint: String? = null, + clientId: String? = null, + postLogoutRedirectUri: String? = null, + state: String? = null, + uiLocales: String? = null, + ): Any { + val apiPath = ("/oauth2/{project_id}/logout" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "id_token_hint" to idTokenHint, + "logout_hint" to logoutHint, + "client_id" to clientId, + "post_logout_redirect_uri" to postLogoutRedirectUri, + "state" to state, + "ui_locales" to uiLocales, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * List the organizations the OAuth2 access token can access. Resolves the token's `organization` authorization details, expanding the `*` wildcard into the concrete set of organizations the user can see. + * + * @param limit Maximum number of organizations to return. Between 1 and 5000. + * @param offset Number of organizations to skip before returning results. Used for pagination. + * @param search Search term to filter your list results. Max length: 256 chars. + * @return [io.appwrite.models.Oauth2OrganizationList] + */ + @JvmOverloads + suspend fun listOrganizations( + limit: Long? = null, + offset: Long? = null, + search: String? = null, + ): io.appwrite.models.Oauth2OrganizationList { + val apiPath = ("/oauth2/{project_id}/organizations" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "limit" to limit, + "offset" to offset, + "search" to search, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2OrganizationList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2OrganizationList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2OrganizationList::class.java, + converter, + ) + } + + /** + * Store an OAuth2 authorization request server-side and receive a short-lived request_uri handle for the authorize endpoint. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param redirectUri Redirect URI where visitor will be redirected after authorization, whether successful or not. + * @param responseType OAuth2 / OIDC response type. + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + * @param state OAuth2 state. You receive this back in the redirect URI. + * @param nonce OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + * @param codeChallenge PKCE code challenge. Required when OAuth2 app is public. + * @param codeChallengeMethod PKCE code challenge method. Required when OAuth2 app is public. + * @param prompt OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + * @param maxAge OIDC max_age parameter for customization of consent screen. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @return [io.appwrite.models.Oauth2PAR] + */ + @JvmOverloads + suspend fun createPAR( + clientId: String, + redirectUri: String, + responseType: String, + scope: String? = null, + state: String? = null, + nonce: String? = null, + codeChallenge: String? = null, + codeChallengeMethod: String? = null, + prompt: String? = null, + maxAge: Long? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + ): io.appwrite.models.Oauth2PAR { + val apiPath = ("/oauth2/{project_id}/par" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "redirect_uri" to redirectUri, + "response_type" to responseType, + "scope" to scope, + "state" to state, + "nonce" to nonce, + "code_challenge" to codeChallenge, + "code_challenge_method" to codeChallengeMethod, + "prompt" to prompt, + "max_age" to maxAge, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2PAR = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2PAR.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2PAR::class.java, + converter, + ) + } + + /** + * List the projects the OAuth2 access token can access. Resolves the token's `project` authorization details, expanding the `*` wildcard into the concrete set of projects the user can see. + * + * @param limit Maximum number of projects to return. Between 1 and 5000. + * @param offset Number of projects to skip before returning results. Used for pagination. + * @param search Search term to filter your list results. Max length: 256 chars. + * @return [io.appwrite.models.Oauth2ProjectList] + */ + @JvmOverloads + suspend fun listProjects( + limit: Long? = null, + offset: Long? = null, + search: String? = null, + ): io.appwrite.models.Oauth2ProjectList { + val apiPath = ("/oauth2/{project_id}/projects" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "limit" to limit, + "offset" to offset, + "search" to search, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2ProjectList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2ProjectList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2ProjectList::class.java, + converter, + ) + } + + /** + * Reject an OAuth2 grant when the user denies consent. Returns the `redirectUrl` the end user should be sent to with an `access_denied` error. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param grantId Grant ID made during authorization, provided to consent screen in URL search params. + * @return [io.appwrite.models.Oauth2Reject] + */ + suspend fun reject( + grantId: String, + ): io.appwrite.models.Oauth2Reject { + val apiPath = ("/oauth2/{project_id}/reject" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "grant_id" to grantId, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Reject = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Reject.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Reject::class.java, + converter, + ) + } + + /** + * Revoke an OAuth2 access token or refresh token. + * + * @param token The access or refresh token to revoke. + * @param tokenTypeHint Type of token to revoke (access_token or refresh_token). + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param clientSecret OAuth2 client secret. Required for confidential apps; omitted for public apps. + * @return [Any] + */ + @JvmOverloads + suspend fun revoke( + token: String, + tokenTypeHint: String? = null, + clientId: String? = null, + clientSecret: String? = null, + ): Any { + val apiPath = ("/oauth2/{project_id}/revoke" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "token" to token, + "token_type_hint" to tokenTypeHint, + "client_id" to clientId, + "client_secret" to clientSecret, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Exchange an OAuth2 authorization code, refresh token, or device code for access and refresh tokens. + * + * @param grantType OAuth2 grant type. Can be one of: `authorization_code`, `refresh_token`, `urn:ietf:params:oauth:grant-type:device_code`. + * @param code Authorization code to be exchanged for access and refresh tokens. Required for `authorization_code` grant type. + * @param refreshToken Refresh token to be exchanged for a new access and refresh tokens. Required for `refresh_token` grant type. + * @param deviceCode Device code obtained from the device authorization endpoint. Required for `urn:ietf:params:oauth:grant-type:device_code` grant type. + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param clientSecret OAuth2 client secret. Required for confidential apps. + * @param codeVerifier PKCE code verifier. Required for public apps. + * @param redirectUri Redirect URI. Required for `authorization_code` grant type. + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @return [io.appwrite.models.Oauth2Token] + */ + @JvmOverloads + suspend fun createToken( + grantType: String, + code: String? = null, + refreshToken: String? = null, + deviceCode: String? = null, + clientId: String? = null, + clientSecret: String? = null, + codeVerifier: String? = null, + redirectUri: String? = null, + resource: String? = null, + audience: String? = null, + ): io.appwrite.models.Oauth2Token { + val apiPath = ("/oauth2/{project_id}/token" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "grant_type" to grantType, + "code" to code, + "refresh_token" to refreshToken, + "device_code" to deviceCode, + "client_id" to clientId, + "client_secret" to clientSecret, + "code_verifier" to codeVerifier, + "redirect_uri" to redirectUri, + "resource" to resource, + "audience" to audience, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Token.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Token::class.java, + converter, + ) + } +} diff --git a/library/src/main/java/io/appwrite/services/Organization.kt b/library/src/main/java/io/appwrite/services/Organization.kt index 264a356e..00ed08a9 100644 --- a/library/src/main/java/io/appwrite/services/Organization.kt +++ b/library/src/main/java/io/appwrite/services/Organization.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Organization service allows you to manage organization-level projects. */ + * The Organization service allows you to manage organization-level projects. + */ class Organization(client: Client) : Service(client) { /** * List app installations on the organization. Any organization member can read installations. diff --git a/library/src/main/java/io/appwrite/services/Presences.kt b/library/src/main/java/io/appwrite/services/Presences.kt index a28448e4..31181a0c 100644 --- a/library/src/main/java/io/appwrite/services/Presences.kt +++ b/library/src/main/java/io/appwrite/services/Presences.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Presences service allows you to track and manage real-time user presence in your project. */ + * The Presences service allows you to track and manage real-time user presence in your project. + */ class Presences(client: Client) : Service(client) { /** * List presence logs. Expired entries are filtered out automatically. diff --git a/library/src/main/java/io/appwrite/services/Push.kt b/library/src/main/java/io/appwrite/services/Push.kt new file mode 100644 index 00000000..1966b8d6 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Push.kt @@ -0,0 +1,763 @@ +package io.appwrite.services + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.hivemq.client.mqtt.datatypes.MqttQos +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import io.appwrite.Client +import io.appwrite.ResolvedTopic +import io.appwrite.Service +import io.appwrite.Topic +import io.appwrite.exceptions.AppwriteException +import java.net.URI +import java.util.UUID +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ConcurrentHashMap + +private const val LOG_TAG = "AppwritePush" + +/** + * Handle for a live subscription created with [Push.subscribe]. + * + * Unlike the realtime service's bare unsubscribe callable, a push subscription carries + * per-subscription state (background delivery, notification title), so [Push.subscribe] + * returns this handle: [unsubscribe] drops it (and closes the connection once the last one + * is gone) and [update] toggles its options live without resubscribing. + */ +class PushSubscription internal constructor( + private val unsubscribeAction: () -> Unit, + private val updateAction: (Boolean?, String?, Boolean?) -> Unit, +) { + /** Drop this subscription. Closes the connection once the last one is gone. */ + fun unsubscribe() = unsubscribeAction() + + /** + * Live-update this subscription's background delivery, notification title and/or + * `retry` (QoS). Only the arguments you pass change; the rest stay as they + * were. Changing `retry` re-subscribes this subscription's topics at the new QoS. + */ + @JvmOverloads + fun update(background: Boolean? = null, title: String? = null, retry: Boolean? = null) = + updateAction(background, title, retry) +} + +/** + * Appwrite native push service — the realtime analog delivered over an MQTT broker, + * without FCM or APNS. + * + * While the app process is alive the connection stays open and each message is handed to + * your callback. A subscription can also opt into background delivery (`background = true`): + * it is saved, and keeps delivering while the app is backgrounded, after the process is + * killed, and after a reboot or an app update, until it is unsubscribed or [close] is called + * (e.g. on sign-out). No service is kept running for it: a scheduled job and alarm wake the + * process every 15 to 60 seconds to reconnect, and the broker keeps the messages sent in + * between and replays them (for `retry = true`). While the process is alive, messages arrive + * immediately. [setForeground] adds a foreground service for immediate delivery even after a kill + * and during Doze. Messages no in-app callback receives (the process was started in the + * background) go to a declared [PushReceiver], and are posted as a notification that opens + * the app unless it handles them. While any background subscription exists, all subscriptions + * share its connection. + * + * Background delivery reconnects with the credential saved at subscribe time, so use a session + * (`Client.setSession`) for it: a JWT expires after minutes. A refused credential stops + * background delivery and reaches [onError], also when it was registered later. + * + * The credential is read off the client — set a JWT or session on it + * (`Client.setJWT` / `Client.setSession`), the same way every other service reads auth. + * + * The connection always keeps its session (clean start off) so the broker can redeliver + * missed messages, and reliability is chosen per subscription via [subscribe]'s + * `retry`. The broker owns the replay window server-side (no MQTT session-expiry + * is set). The client id defaults to one per user and install, so replay resumes across + * restarts; `Client.setPushClientId(...)` overrides it. TLS (and skipping cert verification via a `?tlsInsecure=true` query flag) is + * derived from the endpoint, so the whole connection is described in one place. + * + * ```kotlin + * val client = Client().setEndpoint("...").setProject("...").setJWT(jwt) + * val push = Push(client, context) + * val sub = push.subscribe("user/123/#") { message -> + * println("${'$'}{message.topic}: ${'$'}{message.data}") + * } + * // ...later + * sub.unsubscribe() + * + * // Or, with no topic, the signed-in user's own topic (`users/`): + * push.subscribe { message -> println(message.data) } + * ``` + */ +class Push( + client: Client, + context: Context, +) : Service(client) { + + // The broker location is taken from Client.setPushEndpoint() when set (e.g. + // "mqtts://host:8883"), otherwise from the regular endpoint. + private val pushEndpoint: String = client.endpointPush ?: client.endpoint + + // When no explicit push endpoint is set, default to a `push.` host on the regular endpoint + // (mirroring how realtime derives from the endpoint, on the push subdomain). + private val host: String = + (runCatching { URI(pushEndpoint).host }.getOrNull() ?: "localhost") + .let { if (client.endpointPush != null) it else "push.$it" } + + // Secure by default: TLS on when the endpoint scheme is secure (https/wss/mqtts/ssl), so + // the credential in the CONNECT packet is not sent in the clear. + private val tls: Boolean = + runCatching { URI(pushEndpoint).scheme }.getOrNull()?.lowercase() in listOf("https", "wss", "mqtts", "ssl") + + // Take the port from the push endpoint only (a regular http/https endpoint's port is the + // API port, not the broker's); otherwise fall back to the MQTT default for the TLS mode. + private val port: Int = + client.endpointPush?.let { runCatching { URI(it).port.takeIf { p -> p != -1 } }.getOrNull() } + ?: (if (tls) 8883 else 1883) + + // Skip TLS cert verification (self-signed brokers / testing) via a query flag on the push + // endpoint, e.g. "mqtts://host:8883?tlsInsecure=true" — so the whole connection is + // described by one URL, not a second config knob. + private val tlsInsecure: Boolean = + client.endpointPush?.let { + runCatching { + val query = URI(it).query ?: "" + query.split("&").any { p -> p == "tlsInsecure=true" || p == "tlsInsecure=1" } + }.getOrDefault(false) + } ?: false + + // Read on the MQTT client's threads to tell a closed/replaced client's failures apart. + @Volatile + private var mqtt: Mqtt5AsyncClient? = null + + private var connectedConfig: PushConfig? = null + private val connectLock = Any() + + @Volatile + private var connectionEpoch = 0 + + // Local id -> subscription (filter + callback + per-sub options). The id is never sent to + // the broker; it only lets the same topic carry more than one callback. + private val subscriptions = ConcurrentHashMap() + + // Whether the connection currently lives in the process-wide background host (its own MQTT + // client) rather than here. It moves there while any subscription, live or saved by an + // earlier run, wants background, and back here once none do — one connection either way. + private var backgroundActive = false + + // For background delivery: its saved state, scheduled runs, service and notifications. + private val appContext: Context = context.applicationContext + + init { + // Resume saved background delivery now, instead of at the next scheduled run. + PushBackground.resume(appContext) + } + + private var onOpen: (() -> Unit)? = null + private var onClose: (() -> Unit)? = null + private var onError: ((Throwable) -> Unit)? = null + + private class Sub( + val filter: String, + val callback: (PushMessage) -> Unit, + var background: Boolean, + var title: String?, + var retry: Boolean, + ) { + @Volatile + var client: Mqtt5AsyncClient? = null + } + + // The broker subscription for a filter uses the highest QoS any subscription on it wants, + // so a QoS-0 subscription never downgrades a QoS-1 one sharing the filter. + private fun effectiveQos(filter: String): MqttQos = + if (subscriptions.values.any { it.filter == filter && it.retry }) { + MqttQos.AT_LEAST_ONCE + } else { + MqttQos.AT_MOST_ONCE + } + + /** Register a callback invoked when the connection opens. */ + fun onOpen(callback: () -> Unit): Push { + onOpen = callback + return this + } + + /** Register a callback invoked when the connection closes. */ + fun onClose(callback: () -> Unit): Push { + onClose = callback + return this + } + + /** + * Register a callback invoked on every error the push service hits: a failed connect or + * automatic reconnect, a lost connection, a broker DISCONNECT, a rejected SUBSCRIBE or + * UNSUBSCRIBE, a foreground-service failure, and the failure of a [subscribe], + * [PushSubscription.update] or [PushSubscription.unsubscribe] call (which still throws to + * its caller). When the broker explains a refused connection or a DISCONNECT with an MQTT 5 + * Reason String, that is the error's message. + * + * Errors outside a call (on the MQTT client's or the foreground service's threads) are never + * thrown, so they cannot crash the app: with no callback registered they are logged. A + * failing call still throws to its caller. + */ + fun onError(callback: (Throwable) -> Unit): Push { + onError = callback + PushBackground.errorHandler = { deliverBackgroundError(it) } + // A refused credential that stopped background delivery while no onError was registered. + PushStore.takeStoppedError(appContext)?.let { report(AppwriteException(it)) } + return this + } + + /** + * Run background delivery in a foreground service (with a quiet ongoing + * notification), for immediate delivery even after the process is killed and during Doze, + * instead of only the scheduled runs. Saved, so it applies after restarts too; call it from + * the foreground (Android 12+ refuses to start the service from the background). Apps that + * never enable it can remove the service from their merged manifest. It only affects + * `background = true` subscriptions; `background = false` ones still deliver only while the + * app process is alive. + */ + fun setForeground(enabled: Boolean): Push { + PushBackground.setForeground(appContext, enabled) + return this + } + + /** + * Subscribe to the signed-in user's own topic, `users/` — "messages for me" + * without knowing any topic, e.g. `push.subscribe { message -> ... }`. + * + * The user id is read locally from the credential set on the client: the `userId` claim of + * the JWT (`Client.setJWT`), else the `id` in the session secret (`Client.setSession`). + * Throws when neither yields one. Like FCM/APNs delivery, it defaults to [background] `true` + * (keeps delivering while the app is backgrounded or closed) and [retry] + * `true` (QoS 1); pass either to override. Otherwise the options and the returned + * [PushSubscription] behave exactly as when subscribing to that topic; see the + * `List` overload. + */ + @JvmOverloads + fun subscribe( + background: Boolean = true, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription { + val userId = reporting { resolvePushUserId(client) } + return subscribe(Topic.path(listOf("users", userId)), background, title, retry, callback) + } + + /** + * Subscribe to a single topic. Returns a [PushSubscription] handle. + * + * Request the `POST_NOTIFICATIONS` runtime permission with [requestNotificationPermission] + * before subscribing with `background = true` on Android 13+. + */ + @JvmOverloads + fun subscribe( + topic: String, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = + subscribe(listOf(topic), background, title, retry, callback) + + /** + * Subscribe to a single topic built with [io.appwrite.Topic], e.g. + * `push.subscribe(Topic.path(listOf("user", userId)).any()) { message -> ... }`. + * Returns a [PushSubscription] handle; see the `List` overload for the options. + */ + @JvmOverloads + fun subscribe( + topic: Topic, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = + subscribe(listOf(topic.toString()), background, title, retry, callback) + + /** + * Subscribe to a single topic ended with `all()`, e.g. + * `push.subscribe(Topic.path(listOf("user", userId)).all()) { message -> ... }`. + * Returns a [PushSubscription] handle; see the `List` overload for the options. + */ + @JvmOverloads + fun subscribe( + topic: ResolvedTopic, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = + subscribe(listOf(topic.toString()), background, title, retry, callback) + + /** + * Subscribe to a list mixing raw strings and topics built with [io.appwrite.Topic], e.g. + * `push.subscribe(listOf("news", Topic.path(listOf("chat")).any())) { message -> ... }`. + * Each element must be a `String`, `Topic` or `ResolvedTopic` (Kotlin has no common type for + * them, so this is checked at runtime). Returns a [PushSubscription] handle; see the + * `List` overload for the options. + */ + @JvmOverloads + @JvmName("subscribeTopics") + fun subscribe( + topics: List, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription { + val filters = topics.mapIndexed { index, topic -> + require(topic is String || topic is Topic || topic is ResolvedTopic) { + "topics[$index] must be a String, Topic or ResolvedTopic, got ${topic::class.simpleName}" + } + topic.toString() + } + return subscribe(filters, background, title, retry, callback) + } + + /** + * Subscribe to one or more topics. Returns a [PushSubscription] handle. + * + * Topics are MQTT filters: pass raw strings, or build them with [io.appwrite.Topic] + * through the `Topic` overloads, e.g. `push.subscribe(Topic.path(listOf("user")).any()) { ... }`. + * + * For a foreground subscription this blocks until the broker has acknowledged every + * SUBSCRIBE (SUBACK): the broker only starts routing a topic once its subscription is + * registered, so publishing before the ack races the message ahead of the subscription + * and it is dropped (no retained replay on MQTT). + * + * Set [background] to keep receiving while the app is backgrounded or closed, also after the + * process is killed or the device restarts, and post a notification per matching message + * with [title] (defaulting to the message topic); see the class docs. [retry] chooses this + * subscription's QoS + * (`true` -> QoS 1 default, `false` -> QoS 0). Toggle any of these later with + * [PushSubscription.update]. + * + * Note: a `background` subscription hands the connection to the background host, which + * connects and subscribes asynchronously and retries failures on its next run — so it + * returns once the host has been handed the subscription, **not** after SUBACK (the SUBACK + * guarantee above holds for foreground subscriptions). [onOpen]/[onClose] observe only the + * in-process (foreground) connection, not the background one; [onError] observes both. + */ + @JvmOverloads + fun subscribe( + topics: List, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = reporting { + val ids = mutableListOf() + for (topic in topics) { + val id = UUID.randomUUID().toString() + ids.add(id) + subscriptions[id] = Sub(topic, callback, background, title, retry) + } + + // If any SUBSCRIBE fails, roll this call's registrations back and return to a + // consistent host for whatever subscriptions remain, then rethrow. + try { + if (hostedInBackground()) { + hostInBackground() + } else { + subscribeInProcess(ids) + } + } catch (e: Exception) { + for (id in ids) { + subscriptions.remove(id) + } + if (subscriptions.isEmpty()) { + releaseAll() + } else { + reconcile() + } + throw e + } + + PushSubscription( + { reporting { unsubscribeIds(ids) } }, + { bg, t, rd -> reporting { updateIds(ids, bg, t, rd) } }, + ) + } + + // Every error the service hits goes through here: to onError when one is registered. It is + // never thrown from here (these errors arrive on HiveMQ's or the service's threads, where a + // throw would crash the app), so with no onError it is logged, like Realtime does. + private fun report(error: Throwable) { + val callback = onError + if (callback == null) { + Log.e(LOG_TAG, "Push error", error) + return + } + try { + callback(error) + } catch (callbackError: Exception) { + Log.e(LOG_TAG, "Push onError threw", callbackError) + } + } + + // Run a call that fails by throwing to its caller: the failure also reaches onError when one + // is registered, and is still thrown to the caller, who can catch it. + private fun reporting(block: () -> T): T = try { + block() + } catch (e: Exception) { + if (onError != null) { + report(e) + } + throw e + } + + // report() for an error raised on the MQTT client's (or the background host's) thread. + private fun reportAsync(error: Throwable) = report(error) + + // A background host error, for its errorHandler: delivered to onError when one is registered. + // Returns false when none is, so the host logs it (and keeps a refusal for a later onError). + private fun deliverBackgroundError(error: Throwable): Boolean { + if (onError == null) { + return false + } + report(error) + return true + } + + // Report a fire-and-forget request's failure (e.g. a SUBACK/UNSUBACK error code) unless + // [client] was closed or replaced meanwhile, which fails its pending requests on purpose. + private fun CompletableFuture<*>.reportFailure(client: Mqtt5AsyncClient) { + whenComplete { _, error -> + if (error != null && mqtt === client) { + reportAsync(error) + } + } + } + + // In-process host: connect and register this call's new filters, blocking on each SUBACK. + private fun subscribeInProcess(ids: List) { + while (true) { + val (client, epoch) = synchronized(connectLock) { connect() to connectionEpoch } + try { + for (id in ids) { + val sub = subscriptions[id] ?: continue + if (sub.client === client) { + continue + } + client.subscribeWith() + .topicFilter(sub.filter) + .qos(effectiveQos(sub.filter)) + .callback { publish -> sub.callback(publish.toPushMessage()) } + .send() + .get() + sub.client = client + } + return + } catch (e: Exception) { + if (epoch == connectionEpoch) { + throw e + } + } + } + } + + // Relocate the connection into the background host and sync it with the current + // subscription set (filters, per-sub callbacks/titles), saving the background ones. + private fun hostInBackground() { + if (mqtt != null) { + mqtt?.disconnect() + mqtt = null + } + subscriptions.values.forEach { it.client = null } + PushBackground.errorHandler = { deliverBackgroundError(it) } + PushBackground.host(appContext, buildConfig(), currentListeners()) + backgroundActive = true + } + + // Bring the in-process connection up and (re)register every filter — used when the last + // background subscription goes away and the connection relocates back in-process. + private fun ensureInProcessSubscribed() { + val client = connect() + for (sub in subscriptions.values) { + if (sub.client === client) { + continue + } + client.subscribeWith() + .topicFilter(sub.filter) + .qos(effectiveQos(sub.filter)) + .callback { publish -> sub.callback(publish.toPushMessage()) } + .send() + .reportFailure(client) + sub.client = client + } + } + + // Re-subscribe a filter in-process at its new effective QoS. Unsubscribe first so the + // per-subscribe HiveMQ callbacks are not duplicated, then re-add one per sub on the filter. + private fun resubscribeFilterInProcess(filter: String) { + val client = mqtt ?: return + client.unsubscribeWith().topicFilter(filter).send().reportFailure(client) + val qos = effectiveQos(filter) + for (sub in subscriptions.values.filter { it.filter == filter }) { + client.subscribeWith() + .topicFilter(filter) + .qos(qos) + .callback { publish -> sub.callback(publish.toPushMessage()) } + .send() + .reportFailure(client) + sub.client = client + } + } + + // Ensure the correct host (in-process vs service) for whatever subscriptions remain, and + // sync it. Called after an unsubscribe/update may have flipped whether background is wanted. + // Only acts on a host change; an in-process update that changes QoS is applied by the + // caller (so an unchanged in-process host is not needlessly resubscribed). + private fun reconcile() { + if (subscriptions.isEmpty()) { + return + } + if (hostedInBackground()) { + // Relocate to (or re-sync) the background host, which applies added/removed/QoS-changed topics. + hostInBackground() + } else if (backgroundActive) { + // Relocate back in-process. + backgroundActive = false + PushBackground.release(appContext) + ensureInProcessSubscribed() + } + } + + private fun unsubscribeIds(ids: List) { + val wasService = backgroundActive + val maybeDowngrade = mutableSetOf() + for (id in ids) { + val entry = subscriptions.remove(id) + val filter = entry?.filter + if (entry?.retry == true && filter != null) { + maybeDowngrade.add(filter) + } + // In-process only: drop the broker filter once its last local callback is gone. + if (!wasService && filter != null && subscriptions.values.none { it.filter == filter }) { + mqtt?.let { client -> client.unsubscribeWith().topicFilter(filter).send().reportFailure(client) } + } + } + if (subscriptions.isEmpty()) { + releaseAll() + return + } + // A removed sub may have been the last background one (relocate in-process) or just + // change the service's topic/QoS set (the service re-sync applies the new QoS). + if (wasService) { + reconcile() + return + } + // In-process, a filter that lost its last QoS-1 sub but still has QoS-0 subs must be + // re-subscribed lower, or the broker keeps replaying to at-most-once subs. + for (filter in maybeDowngrade) { + if (subscriptions.values.any { it.filter == filter } && effectiveQos(filter) == MqttQos.AT_MOST_ONCE) { + resubscribeFilterInProcess(filter) + } + } + } + + private fun updateIds(ids: List, background: Boolean?, title: String?, retry: Boolean?) { + val changedFilters = mutableSetOf() + for (id in ids) { + val sub = subscriptions[id] ?: continue + if (background != null) { + sub.background = background + } + if (title != null) { + sub.title = title + } + if (retry != null && retry != sub.retry) { + sub.retry = retry + changedFilters.add(sub.filter) + } + } + val wasService = backgroundActive + // Handle any host change (relocation); a relocated host resubscribes everything fresh. + reconcile() + // Apply QoS changes to a host that did not relocate: the service re-sync above already + // covers the service host, so only the steady in-process host needs an explicit resub. + if (!hostedInBackground() && !wasService) { + for (filter in changedFilters) { + resubscribeFilterInProcess(filter) + } + } + } + + // The connection lives in the background host while any live subscription wants background, + // or an earlier run saved background subscriptions that are still delivered: two connections + // with the same client id would take over each other's broker session. + private fun hostedInBackground(): Boolean = + subscriptions.values.any { it.background } || PushBackground.hasSaved(appContext) + + private fun currentListeners(): List = + subscriptions.values.map { PushListener(setOf(it.filter), it.callback, it.background, it.title, it.retry) } + + /** + * Tear down the connection and drop all subscriptions, including the background ones saved + * by an earlier run, and stop background delivery. Call it on sign-out. + */ + fun close() { + mqtt?.disconnect() + mqtt = null + subscriptions.clear() + backgroundActive = false + PushBackground.stop(appContext) + } + + // The last live subscription is gone: close the connection, keeping any background + // subscriptions saved by an earlier run. + private fun releaseAll() { + mqtt?.disconnect() + mqtt = null + subscriptions.clear() + if (backgroundActive) { + backgroundActive = false + PushBackground.release(appContext) + } + } + + private fun connect(): Mqtt5AsyncClient = synchronized(connectLock) { + val config = buildConfig() + // Reuse the existing client even while it is mid automatic-reconnect. Building a second + // client here would give two clients the same stable id, so they would fight over the + // broker session and callbacks could stay attached to the abandoned one. + mqtt?.let { existing -> + if (config == connectedConfig) { + return existing + } + connectionEpoch++ + existing.disconnect() + mqtt = null + } + + // The listeners surface the lifecycle to onOpen/onClose and every connection error to + // onError, except a refused initial CONNECT: pushConnect throws that one, and the + // failing call reports it. + val client = buildPushClient( + config, + onConnected = { onOpen?.invoke() }, + onDisconnected = { onClose?.invoke() }, + onError = { reportAsync(it) }, + ) + mqtt = client + connectedConfig = config + + try { + client.pushConnect(config) + } catch (e: Exception) { + client.disconnect() + if (mqtt === client) { + mqtt = null + connectedConfig = null + } + throw e + } + + for (sub in subscriptions.values) { + if (sub.client == null || sub.client === client) { + continue + } + client.subscribeWith() + .topicFilter(sub.filter) + .qos(effectiveQos(sub.filter)) + .callback { publish -> sub.callback(publish.toPushMessage()) } + .send() + .reportFailure(client) + sub.client = client + } + + client + } + + private fun buildConfig(): PushConfig { + val (authMethod, credential) = resolvePushAuth(client) + return PushConfig( + host = host, + port = port, + tls = tls, + tlsInsecure = tlsInsecure, + // HiveMQ cannot connect with an empty id (it expects the broker to assign one), so + // when the app did not call Client.setPushClientId() derive a stable id per user and + // install. The broker keeps the replay position per user and client id, so each + // device resumes its own backlog across restarts, and two devices of one user do not + // take over each other's session. + clientId = client.pushClientId?.takeIf { it.isNotEmpty() } + ?: defaultPushClientId(appContext, authMethod, credential), + keepAlive = KEEP_ALIVE_SECONDS, + authMethod = authMethod, + credential = credential, + project = client.config["project"] ?: "", + ) + } + + companion object { + // Fixed keep-alive (PINGREQ interval) in seconds — not exposed as an option. + private const val KEEP_ALIVE_SECONDS = 60 + + /** The notification tap intent's extra holding the message topic. */ + const val EXTRA_TOPIC = PushBackground.EXTRA_TOPIC + + /** The notification tap intent's extra holding the message payload as a string. */ + const val EXTRA_PAYLOAD = PushBackground.EXTRA_PAYLOAD + + /** + * Request the `POST_NOTIFICATIONS` runtime permission (Android 13+). Call from an + * Activity before subscribing with `background = true`; on older versions it is a no-op. + */ + @JvmStatic + @JvmOverloads + fun requestNotificationPermission(activity: Activity, requestCode: Int = 9412) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return + } + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + activity, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + requestCode, + ) + } + } + } +} + +/** Read the credential set on the client (via Client.setJWT / setSession). */ +internal fun resolvePushAuth(client: Client): Pair { + val jwt = client.config["jwt"] ?: client.config["jWT"] + val session = client.config["session"] + return when { + !jwt.isNullOrEmpty() -> "appwrite-jwt" to jwt + !session.isNullOrEmpty() -> "appwrite-session" to session + else -> throw AppwriteException( + "No credential set on the client; call Client.setJWT() or Client.setSession() first.", + ) + } +} + +/** + * The signed-in user's id for the topic-less [Push.subscribe], read locally in the same order as + * [resolvePushAuth]: the JWT's `userId` claim, else the `id` in the session secret (standard + * base64 of `{"id": ..., "secret": ...}`). Throws when neither yields one. + */ +internal fun resolvePushUserId(client: Client): String { + val jwt = client.config["jwt"] ?: client.config["jWT"] + val session = client.config["session"] + val userId = when { + !jwt.isNullOrEmpty() -> userIdFromJwt(jwt) + !session.isNullOrEmpty() -> userIdFromSession(session) + else -> "" + } + if (userId.isEmpty()) { + throw AppwriteException( + "subscribe() without a topic needs a signed-in user: set a JWT or session on the client", + ) + } + return userId +} diff --git a/library/src/main/java/io/appwrite/services/PushBackground.kt b/library/src/main/java/io/appwrite/services/PushBackground.kt new file mode 100644 index 00000000..52517344 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushBackground.kt @@ -0,0 +1,1009 @@ +package io.appwrite.services + +import android.app.AlarmManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.job.JobInfo +import android.app.job.JobScheduler +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.ConnectivityManager +import android.net.Network +import android.os.Build +import android.os.PowerManager +import android.os.SystemClock +import android.util.AtomicFile +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import com.hivemq.client.mqtt.MqttGlobalPublishFilter +import com.hivemq.client.mqtt.datatypes.MqttQos +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish +import io.appwrite.exceptions.AppwriteException +import org.json.JSONArray +import org.json.JSONObject +import java.io.File +import java.io.IOException +import java.util.UUID +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +private const val LOG_TAG = "AppwritePush" + +/** A background subscription saved across restarts: its filter, QoS choice and notification title. */ +internal data class PushEntry( + val filter: String, + val retry: Boolean, + val title: String?, +) + +/** + * The adaptive heartbeat: its interval, the current streak of failed heartbeats, and when the + * interval last shrank (wall-clock ms, as it must survive reboots). + */ +internal data class PushHeartbeat( + val intervalMs: Long, + val failures: Int = 0, + val shrunkAt: Long = 0L, +) + +/** + * The process-wide host of the background push connection, modelled on how socket push + * services survive on Android without keeping a service alive. + * + * The background subscriptions and their connection config are saved to private storage (see + * [PushStore]), so delivery survives the process being killed, a reboot and an app update. A + * job ([PushJobService]) that reschedules itself on every run, backed by an alarm + * ([PushAlarmReceiver]), wakes the process every 15 seconds (60 when the app may use exact + * alarms or is exempt from battery optimisation). Each run holds a short wakelock and + * reconnects when the connection is gone or the default network changed, sends a heartbeat + * when one is due, and re-arms the job. While the process is alive between runs the socket + * stays open and messages arrive immediately; while it is dead the broker keeps the backlog + * for this client id and replays it when the next run subscribes again. + * + * With foreground mode enabled ([Push.setForeground]) a foreground service ([PushService]) also keeps + * the process at foreground priority, so delivery is immediate after a kill and during Doze. + * + * Messages go to the live in-app callbacks that match them. When none match (the process was + * started in the background), a declared [PushReceiver] gets the message, and a notification + * is posted unless it returns true. + */ +internal object PushBackground { + const val CHANNEL_ID = "appwrite-push" + const val NOTIFICATION_ID = 1911 + const val ACTION_TICK = "io.appwrite.push.TICK" + const val ACTION_MESSAGE = "io.appwrite.push.MESSAGE" + const val NOTIFICATION_ICON = "io.appwrite.push.notification_icon" + const val EXTRA_TOPIC = "io.appwrite.push.TOPIC" + const val EXTRA_PAYLOAD = "io.appwrite.push.PAYLOAD" + + private const val SERVICE_CHANNEL_ID = "appwrite-push-service" + private const val JOB_ID = 0x50555348 + private const val INTERVAL_MS = 15_000L + private const val PRIVILEGED_INTERVAL_MS = 60_000L + private const val RESTART_DELAY_MS = 3_000L + // An upper bound covering a run's connect (20 s) and its requests (10 s each); the wakelock is + // released as soon as the run finishes. + private const val WAKE_LOCK_MS = 60_000L + private const val KEEP_ALIVE_SECONDS = 300 + + // The heartbeat interval: below the broker's 1.5 x keep-alive expiry, measured on a clock that + // counts deep sleep. Networks that drop idle connections sooner (carrier NATs) make heartbeats + // fail; after HEARTBEAT_FAILURES in a row the interval shrinks by a quarter, down to + // MIN_HEARTBEAT_MS, and returns to the default HEARTBEAT_RESET_MS after the last shrink, in + // case the device moved to a better network. + const val HEARTBEAT_MS = 180_000L + private const val MIN_HEARTBEAT_MS = 60_000L + private const val HEARTBEAT_FAILURES = 3 + private const val HEARTBEAT_RESET_MS = 3 * 24 * 60 * 60 * 1_000L + private const val REQUEST_TIMEOUT_SECONDS = 10L + private const val CONNECT_TIMEOUT_SECONDS = 20L + + // How long a message waits for a listener that acknowledges it itself (the React Native and + // Flutter bridges, once their callback has run) before it is acknowledged anyway, so one stuck + // callback cannot hold back the acknowledgements after it. + private const val ACK_TIMEOUT_SECONDS = 10L + + // Unsubscribing a filter nothing subscribes to is a harmless round trip the broker always + // acknowledges, so it proves the connection is alive. The broker ignores client PUBLISHes + // without a PUBACK, so a publish cannot serve as the heartbeat. + private const val HEARTBEAT_FILTER = "\$heartbeat" + + @Volatile + var channelName: String = "Notifications" + + @Volatile + var ongoingTitle: String = "Appwrite" + + // Reports errors to the Push instance that hosts its subscriptions here. Returns whether the + // app's onError received the error (false when it registered none). + @Volatile + var errorHandler: ((Throwable) -> Boolean)? = null + + @Volatile + var serviceRunning = false + + // Whether a refused service start was reported, so a job that retries it every run does not + // report the same failure every 15 seconds. + @Volatile + private var serviceStartReported = false + + // The live in-app subscriptions hosted here, each with its callback. Empty after the process + // was restarted in the background, until the app subscribes again. + val listeners = CopyOnWriteArrayList() + + // Guards the saved state below and its copy on disk. + private val lock = Any() + private var loaded = false + private var config: PushConfig? = null + + // Background subscriptions saved by an earlier run that no live subscription has taken over. + private var saved: List = emptyList() + + // Every MQTT call runs on this thread, in order, so the connection state below needs no lock. + private val worker = Executors.newSingleThreadScheduledExecutor { runnable -> + Thread(runnable, "AppwritePushBackground").apply { isDaemon = true } + } + private var mqtt: Mqtt5AsyncClient? = null + private var connectedConfig: PushConfig? = null + private var connectedNetwork: Network? = null + private var lastHeartbeat = 0L + private var heartbeatState: PushHeartbeat? = null + private val subscribed = mutableMapOf() + private var networkCallback: ConnectivityManager.NetworkCallback? = null + + // Worker thread only. While a caller waits for a converge (host with a completion), its + // failures go to that caller, which reports them, instead of onError. + private var waiting = false + private var failure: Throwable? = null + + /** + * Host [newListeners] (the app's live subscriptions) on the background connection with + * [newConfig], save the background ones, and arm the scheduled wake-ups. Saved subscriptions + * from an earlier run stay unless a live subscription on the same filter takes them over + * (with background on or off), or [newConfig] is for another client id (another user), whose + * saved subscriptions are dropped. + * + * With [done], it is called once the connection is up and every filter is subscribed (SUBACK), + * or with the failure, which is then not reported to onError: the caller reports it. + */ + fun host( + context: Context, + newConfig: PushConfig, + newListeners: List, + done: ((Throwable?) -> Unit)? = null, + ) { + val app = context.applicationContext + synchronized(lock) { + load(app) + if (config != null && config?.clientId != newConfig.clientId) { + saved = emptyList() + } + // A live subscription on a saved filter takes it over, whether or not it wants background: + // one that turned background off, or is later unsubscribed, must not leave the saved + // entry delivering in the background. + saved = saved.filter { entry -> newListeners.none { entry.filter in it.topics } } + config = newConfig + listeners.clear() + listeners.addAll(newListeners) + persist(app) + } + activate(app, done) + } + + /** + * Stop hosting the app's live subscriptions, keeping the saved ones from an earlier run. + * Shuts down when none remain. + */ + fun release(context: Context) { + val app = context.applicationContext + val active = synchronized(lock) { + listeners.clear() + persist(app) + isActive() + } + if (active) { + worker.execute { converge(app, heartbeat = false) } + } else { + shutdown(app) + } + } + + /** Stop background delivery completely and forget every saved subscription. */ + fun stop(context: Context) { + val app = context.applicationContext + synchronized(lock) { + listeners.clear() + config = null + saved = emptyList() + loaded = true + PushStore.clearState(app) + } + shutdown(app) + } + + /** Whether subscriptions saved by an earlier run are still delivered in the background. */ + fun hasSaved(context: Context): Boolean = synchronized(lock) { + load(context.applicationContext) + saved.isNotEmpty() + } + + /** Persist the foreground-mode choice and start or stop the foreground service to match. */ + fun setForeground(context: Context, enabled: Boolean) { + val app = context.applicationContext + PushStore.setForeground(app, enabled) + serviceStartReported = false + ensureService(app) + } + + /** Whether the foreground service should run: foreground mode is enabled and there is work to do. */ + fun wantsService(context: Context): Boolean { + val app = context.applicationContext + return PushStore.foreground(app) && synchronized(lock) { + load(app) + isActive() + } + } + + /** Resume saved background delivery when the app starts, without waiting for the next run. */ + fun resume(context: Context) { + val app = context.applicationContext + val active = synchronized(lock) { + load(app) + isActive() + } + if (active) { + tick(app) {} + } + } + + /** + * One scheduled run: under a wakelock, reconnect if needed, send a heartbeat when one is due, + * re-arm the next run, and call [done] once finished. Shuts down when nothing is saved. + */ + fun tick(context: Context, done: () -> Unit) { + val app = context.applicationContext + val wakeLock = acquireWakeLock(app) + worker.execute { + try { + val active = synchronized(lock) { + load(app) + isActive() + } + if (active) { + // Arm the next run first, so a run cut short (killed, or a slow broker outlasting + // the wakelock) still leaves the chain armed. A refused credential in converge() + // shuts down, cancelling it again. + schedule(app, INTERVAL_MS) + ensureService(app) + registerNetworkCallback(app) + converge(app, heartbeat = true) + } else { + shutdown(app) + } + } catch (e: Exception) { + report(e) + } finally { + releaseWakeLock(wakeLock) + done() + } + } + } + + /** Run once more shortly after the task was removed, so an OEM kill that follows is undone. */ + fun scheduleRestart(context: Context) { + schedule(context.applicationContext, RESTART_DELAY_MS, jobToo = false) + } + + /** + * Forget everything held in memory, as the process dying does, keeping only what is saved. + * For tests of what happens after the app's process was killed. + */ + internal fun dropProcessState(context: Context) { + synchronized(lock) { + listeners.clear() + config = null + saved = emptyList() + loaded = false + } + errorHandler = null + unregisterNetworkCallback(context.applicationContext) + worker.submit { disconnect() }.get() + } + + /** Report an error to the hosting Push instance's onError; logged when there is none. */ + fun report(error: Throwable): Boolean { + val delivered = try { + errorHandler?.invoke(error) ?: false + } catch (callbackError: Exception) { + Log.e(LOG_TAG, "Push onError threw", callbackError) + true + } + if (!delivered) { + Log.e(LOG_TAG, "Push background error", error) + } + return delivered + } + + // Must hold [lock]. + private fun load(context: Context) { + if (loaded) { + return + } + loaded = true + val state = PushStore.loadState(context) ?: return + config = state.first + saved = state.second + } + + // Must hold [lock]. + private fun isActive(): Boolean = config != null && (saved.isNotEmpty() || listeners.isNotEmpty()) + + // Must hold [lock]. Every background subscription, live or saved, one per filter and title. + // Subscriptions sharing a filter and title are saved once, with QoS 1 when any of them wants it. + private fun entries(): List = + (saved + listeners.filter { it.background }.flatMap { listener -> listener.topics.map { PushEntry(it, listener.retry, listener.title) } }) + .groupBy { it.filter to it.title } + .map { (key, group) -> PushEntry(key.first, group.any { it.retry }, key.second) } + + // Must hold [lock]. + private fun persist(context: Context) { + val current = config + val entries = entries() + if (current == null || entries.isEmpty()) { + PushStore.clearState(context) + } else { + PushStore.saveState(context, current, entries) + } + } + + // Must hold [lock]. The broker subscriptions wanted, at the highest QoS any subscription on a + // filter asks for. + private fun wanted(): Map { + val qos = linkedMapOf() + fun want(filter: String, retry: Boolean) { + if (retry) { + qos[filter] = MqttQos.AT_LEAST_ONCE + } else if (filter !in qos) { + qos[filter] = MqttQos.AT_MOST_ONCE + } + } + for (listener in listeners) { + listener.topics.forEach { want(it, listener.retry) } + } + saved.forEach { want(it.filter, it.retry) } + return qos + } + + private fun activate(context: Context, done: ((Throwable?) -> Unit)? = null) { + registerNetworkCallback(context) + schedule(context, INTERVAL_MS) + ensureService(context) + worker.execute { converge(context, heartbeat = false, done) } + } + + private fun shutdown(context: Context) { + cancelSchedule(context) + unregisterNetworkCallback(context) + if (serviceRunning) { + context.stopService(Intent(context, PushService::class.java)) + } + worker.execute { disconnect() } + } + + // Worker thread only. Bring the connection to the wanted state: reconnect when the config or + // the default network changed or a due heartbeat fails, then subscribe and unsubscribe the + // difference. With [done], failures go to it (see [waiting]) instead of onError. + private fun converge(context: Context, heartbeat: Boolean, done: ((Throwable?) -> Unit)? = null) { + waiting = done != null + failure = null + try { + convergeOnce(context, heartbeat) + } catch (e: Exception) { + fail(e) + } finally { + waiting = false + done?.invoke(failure) + } + } + + // Worker thread only. Record a failure for the waiting caller, or report it. + private fun fail(error: Throwable) { + if (failure == null) { + failure = error + } + if (!waiting) { + report(error) + } + } + + private fun convergeOnce(context: Context, heartbeat: Boolean) { + val (current, wanted) = synchronized(lock) { config to wanted() } + if (current == null || wanted.isEmpty()) { + disconnect() + return + } + var client = mqtt + if (client != null && (current != connectedConfig || networkChanged(context))) { + disconnect() + client = null + } + if (client != null && heartbeat && SystemClock.elapsedRealtime() - lastHeartbeat >= heartbeatInterval(context)) { + val alive = sendHeartbeat(client) + recordHeartbeat(context, alive) + if (alive) { + lastHeartbeat = SystemClock.elapsedRealtime() + } else { + disconnect() + client = null + } + } + if (client == null) { + client = connect(context, current) ?: return + } + for (filter in subscribed.keys - wanted.keys) { + request { client.unsubscribeWith().topicFilter(filter).send().get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) } + subscribed.remove(filter) + } + for ((filter, qos) in wanted) { + if (subscribed[filter] != qos && subscribe(client, filter, qos)) { + subscribed[filter] = qos + } + } + } + + // Worker thread only. Connect with [config], or report why not and return null. A refused + // credential stops background delivery: retrying it would only be refused again. + private fun connect(context: Context, config: PushConfig): Mqtt5AsyncClient? { + // A failed first connect of a waited-for converge goes to the waiting caller only. + val quiet = waiting + val connected = AtomicBoolean(false) + val client = buildPushClient( + config.copy(keepAlive = KEEP_ALIVE_SECONDS), + onError = { error -> + if (!quiet || connected.get()) { + report(error) + } + }, + onAuthRefused = { error -> worker.execute { refused(context, error) } }, + ) + // Registered before connecting, so the backlog the broker replays right after each + // SUBSCRIBE reaches the app however the subscription was made (including the ones the + // client restores itself after an automatic reconnect). Acknowledged by hand, once the + // message was delivered (see [deliver]). + client.publishes(MqttGlobalPublishFilter.ALL, { publish -> deliver(context, publish) }, true) + try { + client.sendPushConnect(config.copy(keepAlive = KEEP_ALIVE_SECONDS), CONNECT_TIMEOUT_SECONDS) + } catch (e: Exception) { + val cause = (e as? ExecutionException)?.cause ?: e + client.disconnect() + val error = connectError(cause) + when { + isAuthRefusal(cause) -> refused(context, error) + waiting -> fail(error) + // A network failure already reached onError through the client's disconnect + // listener; a refused CONNECT and a timeout did not. + cause is Mqtt5ConnAckException || cause is TimeoutException -> report(error) + } + return null + } + connected.set(true) + mqtt = client + connectedConfig = config + connectedNetwork = activeNetwork(context) + lastHeartbeat = SystemClock.elapsedRealtime() + subscribed.clear() + return client + } + + // Worker thread only. + private fun subscribe(client: Mqtt5AsyncClient, filter: String, qos: MqttQos): Boolean = request { + val subAck = client.subscribeWith().topicFilter(filter).qos(qos).send().get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) + val code = subAck.reasonCodes.firstOrNull { it.isError } + if (code != null) { + throw AppwriteException( + subAck.reasonString.map { it.toString() }.orElse("Subscribing to $filter was refused (reason $code)"), + ) + } + } + + // Worker thread only. The current heartbeat interval, back at the default once a shrink is + // old enough. + private fun heartbeatInterval(context: Context): Long { + val state = heartbeatState ?: PushStore.heartbeat(context).also { heartbeatState = it } + if (state.intervalMs < HEARTBEAT_MS && System.currentTimeMillis() - state.shrunkAt >= HEARTBEAT_RESET_MS) { + saveHeartbeat(context, PushHeartbeat(HEARTBEAT_MS)) + return HEARTBEAT_MS + } + return state.intervalMs + } + + // Worker thread only. Count a failed heartbeat (a connection the network dropped silently), + // shrinking the interval after HEARTBEAT_FAILURES in a row; a delivered one ends the streak. + private fun recordHeartbeat(context: Context, alive: Boolean) { + val state = heartbeatState ?: PushStore.heartbeat(context) + val next = when { + alive -> state.copy(failures = 0) + state.failures + 1 < HEARTBEAT_FAILURES -> state.copy(failures = state.failures + 1) + else -> PushHeartbeat(maxOf(MIN_HEARTBEAT_MS, state.intervalMs * 3 / 4), 0, System.currentTimeMillis()) + } + if (next != state) { + saveHeartbeat(context, next) + } + } + + private fun saveHeartbeat(context: Context, state: PushHeartbeat) { + heartbeatState = state + PushStore.setHeartbeat(context, state) + } + + // Worker thread only. + private fun sendHeartbeat(client: Mqtt5AsyncClient): Boolean = try { + client.unsubscribeWith().topicFilter(HEARTBEAT_FILTER).send().get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) + true + } catch (e: Exception) { + false + } + + // Run a broker request, recording or reporting its failure. Returns whether it succeeded. + private fun request(block: () -> Unit): Boolean = try { + block() + true + } catch (e: Exception) { + fail((e as? ExecutionException)?.cause ?: e) + false + } + + // Worker thread only. + private fun disconnect() { + mqtt?.disconnect() + mqtt = null + connectedConfig = null + connectedNetwork = null + subscribed.clear() + } + + // Worker thread only. The broker refused the credential: report it and stop. With no onError + // registered the message is also saved, and the next onError registration receives it. A + // waiting caller receives it instead. + private fun refused(context: Context, error: Throwable) { + if (waiting) { + fail(error) + } else if (!report(error)) { + PushStore.setStoppedError(context, error.message ?: "Push connection refused") + } + stop(context) + } + + // Deliver a message, then acknowledge it: right away, or once every listener that acknowledges + // messages itself has done so (at most ACK_TIMEOUT_SECONDS later). + private fun deliver(context: Context, publish: Mqtt5Publish) { + val acknowledged = AtomicBoolean(false) + val acknowledge = { + if (acknowledged.compareAndSet(false, true)) { + runCatching { publish.acknowledge() } + } + } + val pending = AtomicInteger(1) + val settle = { + if (pending.decrementAndGet() == 0) { + acknowledge() + } + } + try { + deliverMessage(context, publish.toPushMessage()) { + pending.incrementAndGet() + val once = AtomicBoolean(false) + val ack: () -> Unit = { + if (once.compareAndSet(false, true)) { + settle() + } + } + ack + } + } finally { + settle() + } + if (!acknowledged.get()) { + worker.schedule({ acknowledge() }, ACK_TIMEOUT_SECONDS, TimeUnit.SECONDS) + } + } + + // Deliver a message to the matching live callbacks; with none, to the declared PushReceiver + // and/or a notification per saved subscription that matches it. [deferAck] hands a listener + // that acknowledges messages itself its acknowledgement. + private fun deliverMessage(context: Context, message: PushMessage, deferAck: () -> () -> Unit) { + val matching = listeners.filter { listener -> listener.topics.any { matchesTopic(it, message.topic) } } + // Saved background subscriptions no live one has taken over; they deliver even when a live + // foreground-only subscription on an overlapping filter received the message too. + val entries = synchronized(lock) { saved }.filter { matchesTopic(it.filter, message.topic) } + if (matching.isEmpty() && entries.isEmpty()) { + return + } + for (listener in matching) { + try { + val acknowledging = listener.acknowledgingCallback + if (acknowledging != null) { + acknowledging(message, deferAck()) + } else { + listener.callback(message) + } + } catch (e: Exception) { + report(e) + } + } + val wakeLock = acquireWakeLock(context) + try { + // The app's PushReceiver gets only messages no live callback received. + val handled = matching.isEmpty() && deliverToReceivers(context, message) + val titles = matching.filter { it.background }.map { it.title ?: message.topic } + + if (handled) emptyList() else entries.map { it.title ?: message.topic } + titles.distinct().forEach { notify(context, message, it) } + } finally { + releaseWakeLock(wakeLock) + } + } + + // Call every PushReceiver the app declares. Returns whether one handled the message. + private fun deliverToReceivers(context: Context, message: PushMessage): Boolean { + val intent = Intent(ACTION_MESSAGE).setPackage(context.packageName) + val receivers = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.queryBroadcastReceivers(intent, PackageManager.ResolveInfoFlags.of(0)) + } else { + @Suppress("DEPRECATION") + context.packageManager.queryBroadcastReceivers(intent, 0) + } + var handled = false + for (info in receivers) { + val name = info.activityInfo?.name ?: continue + try { + val receiver = Class.forName(name, true, context.classLoader).getDeclaredConstructor().newInstance() + if (receiver is PushReceiver && receiver.onMessage(context, message)) { + handled = true + } + } catch (e: Exception) { + report(e) + } + } + return handled + } + + /** Post a notification for [message] that opens the app, with the message in its extras. */ + fun notify(context: Context, message: PushMessage, title: String) { + val manager = NotificationManagerCompat.from(context) + if (!manager.areNotificationsEnabled()) { + return + } + createChannels(context) + val id = 31 * message.hashCode() + title.hashCode() + val builder = NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle(title) + .setContentText(message.data) + .setSmallIcon(notificationIcon(context)) + .setAutoCancel(true) + context.packageManager.getLaunchIntentForPackage(context.packageName)?.let { launch -> + launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + .putExtra(EXTRA_TOPIC, message.topic) + .putExtra(EXTRA_PAYLOAD, message.data) + builder.setContentIntent( + PendingIntent.getActivity(context, id, launch, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE), + ) + } + try { + manager.notify(id, builder.build()) + } catch (e: SecurityException) { + // POST_NOTIFICATIONS not granted; nothing to show. + } + } + + /** The ongoing notification the foreground service shows, on its own quiet channel. */ + fun ongoingNotification(context: Context) = NotificationCompat.Builder(context, SERVICE_CHANNEL_ID) + .also { createChannels(context) } + .setContentTitle(ongoingTitle) + .setContentText("Listening for messages") + .setSmallIcon(notificationIcon(context)) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .build() + + private fun createChannels(context: Context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return + } + val manager = context.getSystemService(NotificationManager::class.java) ?: return + manager.createNotificationChannel(NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_HIGH)) + manager.createNotificationChannel( + NotificationChannel(SERVICE_CHANNEL_ID, "Background connection", NotificationManager.IMPORTANCE_LOW), + ) + } + + // The icon named by the app's ``, else a + // generic one. + private fun notificationIcon(context: Context): Int { + val metaData = runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.getApplicationInfo(context.packageName, PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong())) + } else { + @Suppress("DEPRECATION") + context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA) + }.metaData + }.getOrNull() + return metaData?.getInt(NOTIFICATION_ICON, 0)?.takeIf { it != 0 } ?: android.R.drawable.stat_notify_chat + } + + private fun ensureService(context: Context) { + if (!wantsService(context)) { + if (serviceRunning) { + context.stopService(Intent(context, PushService::class.java)) + } + return + } + if (serviceRunning) { + return + } + try { + ContextCompat.startForegroundService(context, Intent(context, PushService::class.java)) + } catch (e: Exception) { + // Android 12+ refuses a foreground-service start from the background; the scheduled + // runs keep delivering, and the next start from the foreground succeeds. + if (!serviceStartReported) { + serviceStartReported = true + report(e) + } + } + } + + // Arm the next run in [delayMs]: the job, and an alarm just after it in case the job is late. + private fun schedule(context: Context, delayMs: Long, jobToo: Boolean = true) { + val interval = if (delayMs == INTERVAL_MS && privileged(context)) PRIVILEGED_INTERVAL_MS else delayMs + if (jobToo) { + try { + val job = JobInfo.Builder(JOB_ID, ComponentName(context, PushJobService::class.java)) + .setMinimumLatency(interval) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setPersisted(true) + .build() + context.getSystemService(JobScheduler::class.java)?.schedule(job) + } catch (e: Exception) { + report(e) + } + } + val alarms = context.getSystemService(AlarmManager::class.java) ?: return + val at = SystemClock.elapsedRealtime() + interval + 1_000L + val intent = tickIntent(context) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarms.canScheduleExactAlarms()) { + alarms.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, at, intent) + } else { + alarms.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, at, intent) + } + } catch (e: SecurityException) { + alarms.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, at, intent) + } + } + + private fun cancelSchedule(context: Context) { + context.getSystemService(JobScheduler::class.java)?.cancel(JOB_ID) + context.getSystemService(AlarmManager::class.java)?.cancel(tickIntent(context)) + } + + private fun tickIntent(context: Context): PendingIntent = PendingIntent.getBroadcast( + context, + 0, + Intent(context, PushAlarmReceiver::class.java).setAction(ACTION_TICK), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + + // Allowed exact alarms or exempt from battery optimisation: the alarm is reliable, so the + // runs can be spaced further apart. + private fun privileged(context: Context): Boolean = + Build.VERSION.SDK_INT < Build.VERSION_CODES.S || + context.getSystemService(AlarmManager::class.java)?.canScheduleExactAlarms() == true || + context.getSystemService(PowerManager::class.java)?.isIgnoringBatteryOptimizations(context.packageName) == true + + private fun activeNetwork(context: Context): Network? = + runCatching { context.getSystemService(ConnectivityManager::class.java)?.activeNetwork }.getOrNull() + + private fun networkChanged(context: Context): Boolean { + val now = activeNetwork(context) + return now != null && connectedNetwork != null && now != connectedNetwork + } + + // Reconnect as soon as the default network changes (e.g. from cellular to Wi-Fi) while the + // process is alive, instead of waiting for the next run to notice. + private fun registerNetworkCallback(context: Context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return + } + synchronized(lock) { + if (networkCallback != null) { + return + } + val callback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + worker.execute { + if (mqtt != null && network != connectedNetwork) { + converge(context, heartbeat = false) + } + } + } + } + try { + context.getSystemService(ConnectivityManager::class.java)?.registerDefaultNetworkCallback(callback) + networkCallback = callback + } catch (e: Exception) { + report(e) + } + } + } + + private fun unregisterNetworkCallback(context: Context) { + val callback = synchronized(lock) { networkCallback.also { networkCallback = null } } ?: return + runCatching { context.getSystemService(ConnectivityManager::class.java)?.unregisterNetworkCallback(callback) } + } + + private fun acquireWakeLock(context: Context): PowerManager.WakeLock? = runCatching { + context.getSystemService(PowerManager::class.java) + ?.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Appwrite:push") + ?.apply { + setReferenceCounted(false) + acquire(WAKE_LOCK_MS) + } + }.getOrNull() + + private fun releaseWakeLock(wakeLock: PowerManager.WakeLock?) { + runCatching { + if (wakeLock?.isHeld == true) { + wakeLock.release() + } + } + } +} + +/** + * Background push state in the app's no-backup directory, so a restored backup on another + * device never carries this install's id or credential: the saved subscriptions with their + * connection config (cleared when background delivery stops), and the settings that outlive + * it (the install id, the foreground-mode choice, a refusal waiting for onError). + */ +internal object PushStore { + private const val STATE_FILE = "appwrite_push_state.json" + private const val SETTINGS_FILE = "appwrite_push_settings.json" + + fun loadState(context: Context): Pair>? = synchronized(this) { + val json = read(context, STATE_FILE) ?: return null + runCatching { + val config = json.getJSONObject("config").let { + PushConfig( + host = it.getString("host"), + port = it.getInt("port"), + tls = it.getBoolean("tls"), + tlsInsecure = it.getBoolean("tlsInsecure"), + clientId = it.getString("clientId"), + keepAlive = it.getInt("keepAlive"), + authMethod = it.getString("authMethod"), + credential = it.getString("credential"), + project = it.getString("project"), + ) + } + val list = json.getJSONArray("entries") + val entries = (0 until list.length()).map { index -> + val entry = list.getJSONObject(index) + PushEntry( + filter = entry.getString("filter"), + retry = entry.getBoolean("retry"), + title = if (entry.isNull("title")) null else entry.getString("title"), + ) + } + config to entries + }.getOrNull() + } + + fun saveState(context: Context, config: PushConfig, entries: List) = synchronized(this) { + val json = JSONObject() + .put( + "config", + JSONObject() + .put("host", config.host) + .put("port", config.port) + .put("tls", config.tls) + .put("tlsInsecure", config.tlsInsecure) + .put("clientId", config.clientId) + .put("keepAlive", config.keepAlive) + .put("authMethod", config.authMethod) + .put("credential", config.credential) + .put("project", config.project), + ) + .put( + "entries", + JSONArray(entries.map { JSONObject().put("filter", it.filter).put("retry", it.retry).put("title", it.title ?: JSONObject.NULL) }), + ) + write(context, STATE_FILE, json) + } + + fun clearState(context: Context) = synchronized(this) { + file(context, STATE_FILE).delete() + } + + /** A random id for this install, created once, which keeps the broker's replay position. */ + fun installId(context: Context): String = synchronized(this) { + val settings = read(context, SETTINGS_FILE) ?: JSONObject() + settings.optString("installId").ifEmpty { + val id = UUID.randomUUID().toString() + write(context, SETTINGS_FILE, settings.put("installId", id)) + id + } + } + + fun foreground(context: Context): Boolean = synchronized(this) { + read(context, SETTINGS_FILE)?.optBoolean("foreground", false) ?: false + } + + fun setForeground(context: Context, enabled: Boolean) = synchronized(this) { + write(context, SETTINGS_FILE, (read(context, SETTINGS_FILE) ?: JSONObject()).put("foreground", enabled)) + } + + /** The saved adaptive heartbeat; the default interval until one was saved. */ + fun heartbeat(context: Context): PushHeartbeat = synchronized(this) { + val json = read(context, SETTINGS_FILE)?.optJSONObject("heartbeat") ?: return PushHeartbeat(PushBackground.HEARTBEAT_MS) + PushHeartbeat( + intervalMs = json.optLong("intervalMs", PushBackground.HEARTBEAT_MS), + failures = json.optInt("failures", 0), + shrunkAt = json.optLong("shrunkAt", 0L), + ) + } + + fun setHeartbeat(context: Context, state: PushHeartbeat) = synchronized(this) { + val heartbeat = JSONObject() + .put("intervalMs", state.intervalMs) + .put("failures", state.failures) + .put("shrunkAt", state.shrunkAt) + write(context, SETTINGS_FILE, (read(context, SETTINGS_FILE) ?: JSONObject()).put("heartbeat", heartbeat)) + } + + fun setStoppedError(context: Context, message: String) = synchronized(this) { + write(context, SETTINGS_FILE, (read(context, SETTINGS_FILE) ?: JSONObject()).put("stoppedError", message)) + } + + /** The refusal that stopped background delivery while no onError was registered, once. */ + fun takeStoppedError(context: Context): String? = synchronized(this) { + val settings = read(context, SETTINGS_FILE) ?: return null + val message = settings.optString("stoppedError").ifEmpty { return null } + settings.remove("stoppedError") + write(context, SETTINGS_FILE, settings) + message + } + + private fun file(context: Context, name: String) = AtomicFile(File(context.noBackupFilesDir, name)) + + private fun read(context: Context, name: String): JSONObject? = + runCatching { JSONObject(String(file(context, name).readFully(), Charsets.UTF_8)) }.getOrNull() + + private fun write(context: Context, name: String, json: JSONObject) { + val file = file(context, name) + val out = try { + file.startWrite() + } catch (e: IOException) { + Log.e(LOG_TAG, "Could not save push state", e) + return + } + try { + out.write(json.toString().toByteArray(Charsets.UTF_8)) + file.finishWrite(out) + } catch (e: IOException) { + file.failWrite(out) + Log.e(LOG_TAG, "Could not save push state", e) + } + } +} diff --git a/library/src/main/java/io/appwrite/services/PushCore.kt b/library/src/main/java/io/appwrite/services/PushCore.kt new file mode 100644 index 00000000..20c56242 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushCore.kt @@ -0,0 +1,374 @@ +package io.appwrite.services + +import android.content.Context +import android.os.Build +import android.util.Base64 +import com.hivemq.client.mqtt.MqttClient +import com.hivemq.client.mqtt.datatypes.MqttUtf8String +import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedContext +import com.hivemq.client.mqtt.lifecycle.MqttDisconnectSource +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.Mqtt5ClientConfig +import com.hivemq.client.mqtt.mqtt5.auth.Mqtt5EnhancedAuthMechanism +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5DisconnectException +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5Auth +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5AuthBuilder +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5EnhancedAuthBuilder +import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import com.hivemq.client.mqtt.mqtt5.message.disconnect.Mqtt5Disconnect +import com.hivemq.client.mqtt.mqtt5.message.disconnect.Mqtt5DisconnectReasonCode +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish +import io.appwrite.exceptions.AppwriteException +import org.json.JSONObject +import java.security.KeyStore +import java.security.Provider +import java.security.cert.X509Certificate +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import javax.net.ssl.ManagerFactoryParameters +import javax.net.ssl.TrustManager +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.TrustManagerFactorySpi +import javax.net.ssl.X509TrustManager + +/** + * A message delivered on a subscribed topic — the MQTT push analog of a realtime event. + */ +data class PushMessage( + val topic: String, + val payload: ByteArray, + val qos: Int, +) { + /** The payload decoded as UTF-8 text, as it was sent. */ + val data: String + get() = String(payload, Charsets.UTF_8) +} + +/** Connection parameters shared between the in-process client and the foreground service. */ +internal data class PushConfig( + val host: String, + val port: Int, + val tls: Boolean, + val tlsInsecure: Boolean, + val clientId: String, + val keepAlive: Int, + val authMethod: String, + val credential: String, + val project: String, +) + +/** + * The userId claim from an Appwrite JWT, used as the default MQTT client id so the + * broker's replay cursor is stable per user. Returns "" when the token is not a decodable JWT + * (e.g. a session secret), so the caller can fall back to the raw credential. + */ +internal fun userIdFromJwt(jwt: String): String { + val parts = jwt.split('.') + if (parts.size < 2) { + return "" + } + return runCatching { + val json = String( + Base64.decode(parts[1], Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING), + Charsets.UTF_8, + ) + JSONObject(json).optString("userId", "") + }.getOrDefault("") +} + +/** The user `id` from an Appwrite session secret, or "" when it is not decodable. */ +internal fun userIdFromSession(session: String): String = runCatching { + val json = String(Base64.decode(session, Base64.DEFAULT), Charsets.UTF_8) + JSONObject(json).optString("id", "") +}.getOrDefault("") + +/** + * The default MQTT client id: one per user and install (`-`, or the install id + * alone when the credential names no user). The broker keeps the replay position per user and + * client id, so each device resumes its own backlog across restarts, and two devices of one user + * do not take over each other's session. + */ +internal fun defaultPushClientId(context: Context, authMethod: String, credential: String): String { + val userId = if (authMethod == "appwrite-session") userIdFromSession(credential) else userIdFromJwt(credential) + val installId = PushStore.installId(context) + return if (userId.isEmpty()) installId else "$userId-$installId" +} + +/** + * Build (but do not connect) an MQTT 5 client from [config]. [onError] receives every connection + * error (a lost connection, a failed automatic reconnect, a broker DISCONNECT) except a refused + * initial CONNECT, which [pushConnect] throws instead. + */ +internal fun buildPushClient( + config: PushConfig, + onConnected: (() -> Unit)? = null, + onDisconnected: (() -> Unit)? = null, + onError: ((Throwable) -> Unit)? = null, + onAuthRefused: ((Throwable) -> Unit)? = null, +): Mqtt5AsyncClient { + // config.clientId is stable per user (see buildConfig), so a reconnect rebuild reuses it + // and resumes the same session. + var builder = MqttClient.builder() + .useMqttVersion5() + .serverHost(config.host) + .serverPort(config.port) + // Recover from an unexpected disconnect (network drop, broker restart) with HiveMQ's + // exponential backoff. A user-initiated disconnect() (see close()) does not trigger it. + // Because clean start is off and the broker resumes the same session, subscriptions and + // the QoS-1 replay cursor persist, so delivery continues without resubscribing (which + // would stack a second per-subscription callback). + .automaticReconnectWithDefaultConfig() + + if (config.clientId.isNotEmpty()) { + builder = builder.identifier(config.clientId) + } + + // Whether this client has connected yet: until it has, a refused CONNECT is final. + val connected = AtomicBoolean(false) + builder = builder.addConnectedListener { + connected.set(true) + onConnected?.invoke() + } + builder = builder.addDisconnectedListener { context -> + // A refused first CONNECT (bad credential, rate limit) is final: stop the automatic + // reconnect so pushConnect fails with the broker's reason instead of retrying forever. + val refused = !connected.get() && context.cause is Mqtt5ConnAckException + // With [onAuthRefused], a credential refused later (a failed automatic reconnect or + // re-auth) is final too, instead of being retried with the same credential forever. + val authRefused = onAuthRefused != null && connected.get() && + context.source != MqttDisconnectSource.USER && isAuthRefusal(context.cause) + if (refused || authRefused) { + context.reconnector.reconnect(false) + } + onDisconnected?.invoke() + if (!refused) { + val error = pushDisconnectError(context) + if (authRefused && error != null) { + // Reported by onAuthRefused, which also stops for good. + onAuthRefused?.invoke(error) + } else { + error?.let { onError?.invoke(it) } + } + } + } + + if (config.tls) { + builder = if (config.tlsInsecure) { + builder.sslConfig() + .trustManagerFactory(PushInsecureTrustManagerFactory) + .hostnameVerifier { _, _ -> true } + .applySslConfig() + } else { + builder.sslWithDefaultConfig() + } + } + + return builder.buildAsync() +} + +/** + * Connect with Appwrite enhanced auth; blocks until CONNACK. Throws an + * [AppwriteException] on failure, whose message is the broker's MQTT 5 + * Reason String when it refused the CONNECT with one. + */ +internal fun Mqtt5AsyncClient.pushConnect(config: PushConfig) { + try { + sendPushConnect(config) + } catch (e: Exception) { + throw connectError((e as? ExecutionException)?.cause ?: e) + } +} + +/** The error a failed CONNECT reports: the broker's Reason String when it refused one. */ +internal fun connectError(cause: Throwable): AppwriteException = + if (cause is Mqtt5ConnAckException) { + connAckError(cause) + } else { + AppwriteException(cause.message ?: "MQTT connection failed") + } + +/** + * Whether [error] is the broker refusing the credential (a CONNECT or re-auth), which retrying + * with the same credential cannot fix, unlike a rate limit or a network failure. + */ +internal fun isAuthRefusal(error: Throwable?): Boolean = when (error) { + is Mqtt5ConnAckException -> error.mqttMessage.reasonCode in setOf( + Mqtt5ConnAckReasonCode.NOT_AUTHORIZED, + Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD, + Mqtt5ConnAckReasonCode.BAD_AUTHENTICATION_METHOD, + Mqtt5ConnAckReasonCode.BANNED, + ) + is Mqtt5DisconnectException -> error.mqttMessage.reasonCode == Mqtt5DisconnectReasonCode.NOT_AUTHORIZED + else -> false +} + +/** Send the CONNECT and wait for the CONNACK, at most [timeoutSeconds] when given. */ +internal fun Mqtt5AsyncClient.sendPushConnect(config: PushConfig, timeoutSeconds: Long? = null) { + val connAck = connectWith() + .enhancedAuth(PushAuthMechanism(config.authMethod, config.credential)) + // Clean start is always off, so the broker keeps this client's session and can + // redeliver missed messages to QoS-1 subscriptions on reconnect. + .cleanStart(false) + .keepAlive(config.keepAlive) + .userProperties() + .add("projectId", config.project) + .applyUserProperties() + .send() + if (timeoutSeconds == null) { + connAck.get() + } else { + connAck.get(timeoutSeconds, TimeUnit.SECONDS) + } +} + +/** + * The error a lost connection reports, or null when it is not an error (a disconnect the app + * asked for, or a broker DISCONNECT with a success reason code). The broker explains a refused + * CONNECT or a DISCONNECT it sends with an MQTT 5 Reason String, which becomes the message. + */ +internal fun pushDisconnectError(context: MqttClientDisconnectedContext): Throwable? { + val cause = context.cause + return when { + context.source == MqttDisconnectSource.USER -> null + cause is Mqtt5ConnAckException -> connAckError(cause) + cause is Mqtt5DisconnectException && context.source == MqttDisconnectSource.SERVER -> { + val disconnect = cause.mqttMessage + if (disconnect.reasonCode.isError) { + AppwriteException( + disconnect.reasonString.map { it.toString() } + .orElse("Disconnected by the broker (reason ${disconnect.reasonCode})"), + ) + } else { + null + } + } + else -> cause + } +} + +/** A refused CONNECT, with the CONNACK Reason String as its message when the broker sent one. */ +internal fun connAckError(e: Mqtt5ConnAckException): AppwriteException = + AppwriteException( + e.mqttMessage.reasonString.map { it.toString() }.orElse(e.message ?: "MQTT connection refused"), + ) + +internal fun Mqtt5Publish.toPushMessage(): PushMessage = + PushMessage( + topic = topic.toString(), + payload = payloadAsBytes, + qos = qos.code, + ) + +/** + * Single-step MQTT 5 enhanced auth: the method and credential are placed in the CONNECT + * packet and the broker accepts them in the CONNACK, with no AUTH round-trip. + */ +internal class PushAuthMechanism( + private val method: String, + private val credential: String, +) : Mqtt5EnhancedAuthMechanism { + + override fun getMethod(): MqttUtf8String = MqttUtf8String.of(method) + + override fun getTimeout(): Int = 30 + + override fun onAuth( + clientConfig: Mqtt5ClientConfig, + connect: Mqtt5Connect, + authBuilder: Mqtt5EnhancedAuthBuilder, + ): CompletableFuture { + authBuilder.data(credential.toByteArray(Charsets.UTF_8)) + return CompletableFuture.completedFuture(null) + } + + override fun onReAuth( + clientConfig: Mqtt5ClientConfig, + authBuilder: Mqtt5AuthBuilder, + ): CompletableFuture { + authBuilder.data(credential.toByteArray(Charsets.UTF_8)) + return CompletableFuture.completedFuture(null) + } + + override fun onContinue( + clientConfig: Mqtt5ClientConfig, + auth: Mqtt5Auth, + authBuilder: Mqtt5AuthBuilder, + ): CompletableFuture = CompletableFuture.completedFuture(true) + + override fun onAuthSuccess( + clientConfig: Mqtt5ClientConfig, + connAck: Mqtt5ConnAck, + ): CompletableFuture = CompletableFuture.completedFuture(true) + + override fun onReAuthSuccess( + clientConfig: Mqtt5ClientConfig, + auth: Mqtt5Auth, + ): CompletableFuture = CompletableFuture.completedFuture(true) + + override fun onAuthRejected(clientConfig: Mqtt5ClientConfig, connAck: Mqtt5ConnAck) = Unit + + override fun onReAuthRejected(clientConfig: Mqtt5ClientConfig, disconnect: Mqtt5Disconnect) = Unit + + override fun onAuthError(clientConfig: Mqtt5ClientConfig, cause: Throwable) = Unit + + override fun onReAuthError(clientConfig: Mqtt5ClientConfig, cause: Throwable) = Unit +} + +/** + * A [TrustManagerFactory] that accepts any certificate — used when `tlsInsecure` is set + * (for a broker whose certificate does not chain to a public root). + */ +internal object PushInsecureTrustManagerFactory : TrustManagerFactory( + object : TrustManagerFactorySpi() { + override fun engineInit(keyStore: KeyStore?) = Unit + + override fun engineInit(parameters: ManagerFactoryParameters?) = Unit + + override fun engineGetTrustManagers(): Array = arrayOf( + object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) = Unit + + override fun checkServerTrusted(chain: Array?, authType: String?) = Unit + + override fun getAcceptedIssuers(): Array = emptyArray() + }, + ) + }, + @Suppress("DEPRECATION") object : Provider("AppwriteInsecure", 1.0, "insecure trust-all") {}, + "AppwriteInsecure", +) + +/** A live in-app subscription hosted in the background: its topics, callback, notification opt-in and QoS. */ +internal class PushListener( + val topics: Set, + val callback: (PushMessage) -> Unit, + val background: Boolean, + val title: String?, + val retry: Boolean, + // Called instead of [callback] when set, with the acknowledgement to run once the message was + // handled: the React Native and Flutter bridges acknowledge after their callback has run. + val acknowledgingCallback: ((PushMessage, () -> Unit) -> Unit)? = null, +) + +/** MQTT topic-filter match with '+' (single level) and '#' (multi level). */ +internal fun matchesTopic(filter: String, topic: String): Boolean { + val filterParts = filter.split('/') + val topicParts = topic.split('/') + for (i in filterParts.indices) { + if (filterParts[i] == "#") { + return true + } + if (i >= topicParts.size) { + return false + } + if (filterParts[i] != "+" && filterParts[i] != topicParts[i]) { + return false + } + } + return filterParts.size == topicParts.size +} diff --git a/library/src/main/java/io/appwrite/services/PushReceiver.kt b/library/src/main/java/io/appwrite/services/PushReceiver.kt new file mode 100644 index 00000000..92d04727 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushReceiver.kt @@ -0,0 +1,33 @@ +package io.appwrite.services + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +/** + * Handles background push messages in code when no in-app callback is listening, e.g. after + * the process was started in the background to reconnect. Subclass it and declare it in your + * manifest with the push message action: + * + * ```xml + * + * + * + * + * + * ``` + * + * The SDK calls [onMessage] directly on a background thread, under a wakelock of about ten + * seconds, so hand longer work to WorkManager. Messages a live callback receives do not + * reach it. + */ +abstract class PushReceiver : BroadcastReceiver() { + /** + * Handle [message]. Return true when you handled it yourself, so the SDK does not post its + * default notification; return false to have it posted as well. + */ + abstract fun onMessage(context: Context, message: PushMessage): Boolean + + // Messages arrive through onMessage, never as a broadcast. + final override fun onReceive(context: Context, intent: Intent) = Unit +} diff --git a/library/src/main/java/io/appwrite/services/PushService.kt b/library/src/main/java/io/appwrite/services/PushService.kt new file mode 100644 index 00000000..e2a05f89 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushService.kt @@ -0,0 +1,67 @@ +package io.appwrite.services + +import android.app.Service +import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Build +import android.os.IBinder +import androidx.core.app.ServiceCompat + +/** + * The foreground mode of background push delivery ([Push.setForeground]): a foreground service + * that keeps the process at foreground priority, so the connection held by the process stays + * up and delivers immediately, also during Doze. The system restarts it after a kill, and it + * restores the saved subscriptions. Started by the SDK; not meant to be started directly. + */ +class PushService : Service() { + override fun onBind(intent: Intent?): IBinder? = null + + override fun onCreate() { + super.onCreate() + PushBackground.serviceRunning = true + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (!PushBackground.wantsService(this)) { + stopSelf() + return START_NOT_STICKY + } + try { + startForegroundCompat() + } catch (e: Exception) { + // E.g. a background start refused on Android 12+; the scheduled runs keep delivering. + stopSelf() + PushBackground.report(e) + return START_NOT_STICKY + } + PushBackground.tick(this) {} + // Recreated after a kill with a null intent; the saved subscriptions are restored then. + return START_STICKY + } + + private fun startForegroundCompat() { + val notification = PushBackground.ongoingNotification(this) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ServiceCompat.startForeground( + this, + PushBackground.NOTIFICATION_ID, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING, + ) + } else { + startForeground(PushBackground.NOTIFICATION_ID, notification) + } + } + + // Some devices kill the process shortly after its task is swiped away; a run a few seconds + // later restarts the service. + override fun onTaskRemoved(rootIntent: Intent?) { + PushBackground.scheduleRestart(this) + super.onTaskRemoved(rootIntent) + } + + override fun onDestroy() { + PushBackground.serviceRunning = false + super.onDestroy() + } +} diff --git a/library/src/main/java/io/appwrite/services/PushWakeups.kt b/library/src/main/java/io/appwrite/services/PushWakeups.kt new file mode 100644 index 00000000..091fd528 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushWakeups.kt @@ -0,0 +1,46 @@ +package io.appwrite.services + +import android.app.job.JobParameters +import android.app.job.JobService +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +/** + * The scheduled run of background push delivery: reconnects and re-arms itself. Scheduled + * by the SDK; not meant to be started directly. + */ +class PushJobService : JobService() { + override fun onStartJob(params: JobParameters): Boolean { + PushBackground.tick(this) { jobFinished(params, false) } + return true + } + + // The run re-arms the next one itself, so a stopped run needs no retry. + override fun onStopJob(params: JobParameters): Boolean = false +} + +/** + * The alarm that backs up the scheduled job when it runs late. Scheduled by the SDK; not meant + * to be sent directly. + */ +class PushAlarmReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != PushBackground.ACTION_TICK) { + return + } + val pending = goAsync() + PushBackground.tick(context) { pending.finish() } + } +} + +/** Resumes background push delivery after a reboot or an app update. */ +class PushBootReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_BOOT_COMPLETED && intent.action != Intent.ACTION_MY_PACKAGE_REPLACED) { + return + } + val pending = goAsync() + PushBackground.tick(context) { pending.finish() } + } +} diff --git a/library/src/main/java/io/appwrite/services/Realtime.kt b/library/src/main/java/io/appwrite/services/Realtime.kt index 01987db8..dd2720f3 100644 --- a/library/src/main/java/io/appwrite/services/Realtime.kt +++ b/library/src/main/java/io/appwrite/services/Realtime.kt @@ -432,7 +432,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { launch(IO) { if (isStale(webSocket)) return@launch try { - val message = text.fromJson() + val message = RealtimeResponse.from(text.fromJson>()) when (message.type) { TYPE_ERROR -> handleResponseError(message) TYPE_CONNECTED -> handleResponseConnected(message) @@ -458,13 +458,19 @@ class Realtime(client: Client) : Service(client), CoroutineScope { } private fun handleResponseError(message: RealtimeResponse) { - val ex = message.data?.jsonCast() ?: RuntimeException("Data is not present") - throw ex + val data = message.data as? Map<*, *> ?: throw RuntimeException("Data is not present") + throw AppwriteException( + message = data["message"] as? String, + code = (data["code"] as? Number)?.toInt(), + type = data["type"] as? String, + response = data["response"] as? String, + ) } private suspend fun handleResponseEvent(message: RealtimeResponse) { - val messageData = message.data?.jsonCast>() ?: return - val event = messageData.jsonCast>() + @Suppress("UNCHECKED_CAST") + val messageData = message.data as? Map ?: return + val event = RealtimeResponseEvent.from(messageData) ?: return if (event.channels.isEmpty()) { return diff --git a/library/src/main/java/io/appwrite/services/Storage.kt b/library/src/main/java/io/appwrite/services/Storage.kt index cc4f8608..6fe1f966 100644 --- a/library/src/main/java/io/appwrite/services/Storage.kt +++ b/library/src/main/java/io/appwrite/services/Storage.kt @@ -12,7 +12,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Storage service allows you to manage your project files. */ + * The Storage service allows you to manage your project files. + */ class Storage(client: Client) : Service(client) { /** * Get a list of all the user files. You can use the query params to filter your results. @@ -114,9 +115,9 @@ class Storage(client: Client) : Service(client) { apiParams, responseType = io.appwrite.models.File::class.java, converter, - paramName, - idParamName, - onProgress, + paramName = paramName, + idParamName = idParamName, + onProgress = onProgress, ) } diff --git a/library/src/main/java/io/appwrite/services/TablesDB.kt b/library/src/main/java/io/appwrite/services/TablesDB.kt index 7a8881df..34217c41 100644 --- a/library/src/main/java/io/appwrite/services/TablesDB.kt +++ b/library/src/main/java/io/appwrite/services/TablesDB.kt @@ -10,21 +10,25 @@ import okhttp3.Cookie import java.io.File /** - * The TablesDB service allows you to create structured tables of columns, query and filter lists of rows */ + * The TablesDB service allows you to create structured tables of columns, query and filter lists of rows + */ class TablesDB(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/tablesdb/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/main/java/io/appwrite/services/Teams.kt b/library/src/main/java/io/appwrite/services/Teams.kt index 7708fcca..7227eba6 100644 --- a/library/src/main/java/io/appwrite/services/Teams.kt +++ b/library/src/main/java/io/appwrite/services/Teams.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources */ + * The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources + */ class Teams(client: Client) : Service(client) { /** * Get a list of all the teams in which the current user is a member. You can use the parameters to filter your results. @@ -77,7 +78,7 @@ class Teams(client: Client) : Service(client) { * * @param teamId Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param name Team name. Max length: 128 chars. - * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long. + * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long. * @return [io.appwrite.models.Team] */ @JvmOverloads @@ -117,7 +118,7 @@ class Teams(client: Client) : Service(client) { * * @param teamId Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param name Team name. Max length: 128 chars. - * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long. + * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long. * @return [io.appwrite.models.Team] */ @JvmOverloads diff --git a/library/src/main/java/io/appwrite/services/VectorsDB.kt b/library/src/main/java/io/appwrite/services/VectorsDB.kt index 9fa2f291..9ad46da2 100644 --- a/library/src/main/java/io/appwrite/services/VectorsDB.kt +++ b/library/src/main/java/io/appwrite/services/VectorsDB.kt @@ -10,21 +10,25 @@ import okhttp3.Cookie import java.io.File /** - * */ + * + */ class VectorsDB(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/vectorsdb/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/test/java/io/appwrite/ClientJavaInteropTest.java b/library/src/test/java/io/appwrite/ClientJavaInteropTest.java index 3019bb94..ea50bcff 100644 --- a/library/src/test/java/io/appwrite/ClientJavaInteropTest.java +++ b/library/src/test/java/io/appwrite/ClientJavaInteropTest.java @@ -22,4 +22,13 @@ public void endpointSettersAreFluentFromJava() { assertEquals("https://cloud.appwrite.io/v1", client.getEndpoint()); assertEquals("wss://cloud.appwrite.io/v1", client.getEndpointRealtime()); } + + @Test + public void positionalSelfSignedFollowsTheRealtimeEndpoint() { + Context context = ApplicationProvider.getApplicationContext(); + Client client = new Client(context, "https://cloud.appwrite.io/v1", "wss://cloud.appwrite.io/v1", true); + + assertEquals("https://cloud.appwrite.io/v1", client.getEndpoint()); + assertEquals("wss://cloud.appwrite.io/v1", client.getEndpointRealtime()); + } } diff --git a/library/src/test/java/io/appwrite/TopicTest.kt b/library/src/test/java/io/appwrite/TopicTest.kt new file mode 100644 index 00000000..0dd65bf0 --- /dev/null +++ b/library/src/test/java/io/appwrite/TopicTest.kt @@ -0,0 +1,53 @@ +package io.appwrite + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Test + +class TopicTest { + private fun assertPathFails(levels: List) { + assertThrows(IllegalArgumentException::class.java) { Topic.path(levels) } + assertThrows(IllegalArgumentException::class.java) { Topic.any().path(levels) } + } + + @Test + fun pathJoinsLevels() { + assertEquals("user/123/notification", Topic.path(listOf("user", "123", "notification")).toString()) + } + + @Test + fun pathAppendsLevels() { + assertEquals( + "org/42/user/123/notification", + Topic.path(listOf("org", "42", "user", "123")).path(listOf("notification")).toString(), + ) + } + + @Test + fun anyAppendsSingleLevelWildcard() { + assertEquals("user/+/notification", Topic.path(listOf("user")).any().path(listOf("notification")).toString()) + assertEquals("chat/+/+/message", Topic.path(listOf("chat")).any().any().path(listOf("message")).toString()) + } + + @Test + fun allEndsTopic() { + assertEquals("org/+/logs/#", Topic.path(listOf("org")).any().path(listOf("logs")).all().toString()) + } + + @Test + fun staticAnyAndAll() { + assertEquals("+/notification", Topic.any().path(listOf("notification")).toString()) + assertEquals("#", Topic.all().toString()) + } + + @Test + fun pathRejectsInvalidLevels() { + assertPathFails(emptyList()) + assertPathFails(listOf("user", "")) + assertPathFails(listOf("user/123")) + assertPathFails(listOf("+")) + assertPathFails(listOf("user", "a+b")) + assertPathFails(listOf("user", "#")) + assertPathFails(listOf("a#b")) + } +} From 27ab125b9a62ee1d8bd32600a8ed11cc1a8306dc Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 3 Oct 2026 14:17:09 +0100 Subject: [PATCH 11/12] chore: update Android SDK to 29.0.0 --- CHANGELOG.md | 24 + README.md | 45 +- .../account/create-email-password-session.md | 1 + .../java/account/create-id-token-session.md | 2 +- docs/examples/java/account/create-jwt.md | 2 +- docs/examples/java/account/delete-sessions.md | 18 +- docs/examples/java/account/list-sessions.md | 18 +- .../java/apps/create-installation-token.md | 27 + docs/examples/java/apps/create-key.md | 26 + docs/examples/java/apps/create-secret.md | 26 + docs/examples/java/apps/create.md | 44 + .../examples/java/apps/delete-installation.md | 27 + docs/examples/java/apps/delete-key.md | 27 + docs/examples/java/apps/delete-secret.md | 27 + docs/examples/java/apps/delete-tokens.md | 26 + docs/examples/java/apps/delete.md | 26 + docs/examples/java/apps/get-installation.md | 27 + docs/examples/java/apps/get-key.md | 27 + docs/examples/java/apps/get-secret.md | 27 + docs/examples/java/apps/get.md | 26 + .../java/apps/list-installation-scopes.md | 22 + docs/examples/java/apps/list-installations.md | 28 + docs/examples/java/apps/list-keys.md | 28 + .../java/apps/list-o-auth-2-scopes.md | 22 + docs/examples/java/apps/list-secrets.md | 28 + docs/examples/java/apps/list.md | 27 + docs/examples/java/apps/update-team.md | 27 + docs/examples/java/apps/update.md | 45 + docs/examples/java/avatars/delete-photo.md | 22 + docs/examples/java/avatars/get-browser.md | 6 +- docs/examples/java/avatars/get-credit-card.md | 6 +- docs/examples/java/avatars/get-flag.md | 6 +- docs/examples/java/avatars/get-image.md | 4 +- docs/examples/java/avatars/get-initials.md | 4 +- docs/examples/java/avatars/get-photo.md | 6 +- docs/examples/java/avatars/get-qr.md | 4 +- docs/examples/java/avatars/get-screenshot.md | 19 +- docs/examples/java/avatars/update-photo.md | 27 + .../java/databases/create-transaction.md | 2 +- .../databases/decrement-document-attribute.md | 4 +- .../databases/increment-document-attribute.md | 4 +- .../examples/java/databases/list-documents.md | 2 +- .../java/databases/list-transactions.md | 1 + .../java/documentsdb/create-documents.md | 9 +- .../java/documentsdb/create-transaction.md | 2 +- .../decrement-document-attribute.md | 4 +- .../increment-document-attribute.md | 4 +- .../java/documentsdb/list-documents.md | 2 +- .../java/documentsdb/list-transactions.md | 1 + docs/examples/java/graphql/mutation.md | 4 +- docs/examples/java/graphql/query.md | 4 +- docs/examples/java/locale/list-codes.md | 18 +- docs/examples/java/locale/list-continents.md | 18 +- .../examples/java/locale/list-countries-eu.md | 18 +- .../java/locale/list-countries-phones.md | 18 +- docs/examples/java/locale/list-countries.md | 18 +- docs/examples/java/locale/list-currencies.md | 18 +- docs/examples/java/locale/list-languages.md | 18 +- docs/examples/java/oauth2/approve.md | 28 + docs/examples/java/oauth2/authorize-post.md | 39 + docs/examples/java/oauth2/authorize.md | 39 + .../oauth2/create-device-authorization.md | 30 + docs/examples/java/oauth2/create-grant.md | 26 + docs/examples/java/oauth2/create-par.md | 38 + docs/examples/java/oauth2/create-token.md | 35 + docs/examples/java/oauth2/get-grant.md | 26 + .../java/oauth2/list-organizations.md | 28 + docs/examples/java/oauth2/list-projects.md | 28 + docs/examples/java/oauth2/logout-post.md | 31 + docs/examples/java/oauth2/logout.md | 31 + docs/examples/java/oauth2/reject.md | 26 + docs/examples/java/oauth2/revoke.md | 29 + docs/examples/java/presences/list.md | 2 +- .../examples/java/storage/get-file-preview.md | 14 +- .../java/tablesdb/create-transaction.md | 2 +- .../java/tablesdb/decrement-row-column.md | 4 +- .../java/tablesdb/increment-row-column.md | 4 +- docs/examples/java/tablesdb/list-rows.md | 2 +- .../java/tablesdb/list-transactions.md | 1 + docs/examples/java/teams/create-membership.md | 2 +- docs/examples/java/teams/update-membership.md | 2 +- .../java/vectorsdb/create-operations.md | 5 +- docs/examples/java/vectorsdb/create-query.md | 2 +- .../java/vectorsdb/create-transaction.md | 2 +- .../examples/java/vectorsdb/list-documents.md | 2 +- .../java/vectorsdb/list-transactions.md | 1 + .../account/create-email-password-session.md | 1 + .../kotlin/account/delete-sessions.md | 4 +- docs/examples/kotlin/account/list-sessions.md | 4 +- .../kotlin/apps/create-installation-token.md | 16 + docs/examples/kotlin/apps/create-key.md | 15 + docs/examples/kotlin/apps/create-secret.md | 15 + docs/examples/kotlin/apps/create.md | 33 + .../kotlin/apps/delete-installation.md | 16 + docs/examples/kotlin/apps/delete-key.md | 16 + docs/examples/kotlin/apps/delete-secret.md | 16 + docs/examples/kotlin/apps/delete-tokens.md | 15 + docs/examples/kotlin/apps/delete.md | 15 + docs/examples/kotlin/apps/get-installation.md | 16 + docs/examples/kotlin/apps/get-key.md | 16 + docs/examples/kotlin/apps/get-secret.md | 16 + docs/examples/kotlin/apps/get.md | 15 + .../kotlin/apps/list-installation-scopes.md | 13 + .../kotlin/apps/list-installations.md | 17 + docs/examples/kotlin/apps/list-keys.md | 17 + .../kotlin/apps/list-o-auth-2-scopes.md | 13 + docs/examples/kotlin/apps/list-secrets.md | 17 + docs/examples/kotlin/apps/list.md | 16 + docs/examples/kotlin/apps/update-team.md | 16 + docs/examples/kotlin/apps/update.md | 34 + docs/examples/kotlin/avatars/delete-photo.md | 13 + .../examples/kotlin/avatars/get-screenshot.md | 7 +- docs/examples/kotlin/avatars/update-photo.md | 16 + .../databases/decrement-document-attribute.md | 4 +- .../databases/increment-document-attribute.md | 4 +- .../kotlin/databases/list-transactions.md | 1 + .../kotlin/documentsdb/create-documents.md | 9 +- .../decrement-document-attribute.md | 4 +- .../increment-document-attribute.md | 4 +- .../kotlin/documentsdb/list-transactions.md | 1 + docs/examples/kotlin/graphql/mutation.md | 4 +- docs/examples/kotlin/graphql/query.md | 4 +- docs/examples/kotlin/locale/list-codes.md | 4 +- .../examples/kotlin/locale/list-continents.md | 4 +- .../kotlin/locale/list-countries-eu.md | 4 +- .../kotlin/locale/list-countries-phones.md | 4 +- docs/examples/kotlin/locale/list-countries.md | 4 +- .../examples/kotlin/locale/list-currencies.md | 4 +- docs/examples/kotlin/locale/list-languages.md | 4 +- docs/examples/kotlin/oauth2/approve.md | 17 + docs/examples/kotlin/oauth2/authorize-post.md | 28 + docs/examples/kotlin/oauth2/authorize.md | 28 + .../oauth2/create-device-authorization.md | 19 + docs/examples/kotlin/oauth2/create-grant.md | 15 + docs/examples/kotlin/oauth2/create-par.md | 27 + docs/examples/kotlin/oauth2/create-token.md | 24 + docs/examples/kotlin/oauth2/get-grant.md | 15 + .../kotlin/oauth2/list-organizations.md | 17 + docs/examples/kotlin/oauth2/list-projects.md | 17 + docs/examples/kotlin/oauth2/logout-post.md | 20 + docs/examples/kotlin/oauth2/logout.md | 20 + docs/examples/kotlin/oauth2/reject.md | 15 + docs/examples/kotlin/oauth2/revoke.md | 18 + .../kotlin/storage/get-file-preview.md | 2 +- .../kotlin/tablesdb/decrement-row-column.md | 4 +- .../kotlin/tablesdb/increment-row-column.md | 4 +- .../kotlin/tablesdb/list-transactions.md | 1 + .../kotlin/teams/create-membership.md | 2 +- .../kotlin/teams/update-membership.md | 2 +- .../kotlin/vectorsdb/create-operations.md | 5 +- .../kotlin/vectorsdb/list-transactions.md | 1 + library/build.gradle.kts | 5 +- library/consumer-rules.pro | 27 + library/src/main/AndroidManifest.xml | 36 +- library/src/main/java/io/appwrite/Client.kt | 61 +- library/src/main/java/io/appwrite/ID.kt | 4 +- library/src/main/java/io/appwrite/Operator.kt | 7 + library/src/main/java/io/appwrite/Query.kt | 14 +- library/src/main/java/io/appwrite/Topic.kt | 68 ++ .../io/appwrite/cookies/InternalCookie.kt | 32 + .../stores/SharedPreferencesCookieStore.kt | 12 +- .../java/io/appwrite/enums/OAuthProvider.kt | 2 + .../main/java/io/appwrite/models/Account.kt | 266 +++++ .../src/main/java/io/appwrite/models/App.kt | 229 ++++ .../main/java/io/appwrite/models/AppKey.kt | 93 ++ .../java/io/appwrite/models/AppKeyList.kt | 37 + .../main/java/io/appwrite/models/AppScope.kt | 61 + .../java/io/appwrite/models/AppScopeList.kt | 37 + .../main/java/io/appwrite/models/AppSecret.kt | 93 ++ .../java/io/appwrite/models/AppSecretList.kt | 37 + .../io/appwrite/models/AppSecretPlaintext.kt | 93 ++ .../main/java/io/appwrite/models/AppsList.kt | 37 + .../main/java/io/appwrite/models/InputFile.kt | 6 +- .../java/io/appwrite/models/Oauth2Approve.kt | 29 + .../io/appwrite/models/Oauth2Authorize.kt | 37 + .../models/Oauth2DeviceAuthorization.kt | 69 ++ .../java/io/appwrite/models/Oauth2Grant.kt | 117 ++ .../io/appwrite/models/Oauth2Organization.kt | 29 + .../appwrite/models/Oauth2OrganizationList.kt | 37 + .../main/java/io/appwrite/models/Oauth2PAR.kt | 37 + .../java/io/appwrite/models/Oauth2Project.kt | 45 + .../io/appwrite/models/Oauth2ProjectList.kt | 37 + .../java/io/appwrite/models/Oauth2Reject.kt | 29 + .../java/io/appwrite/models/Oauth2Token.kt | 77 ++ .../java/io/appwrite/models/RealtimeModels.kt | 23 +- .../main/java/io/appwrite/services/Account.kt | 35 +- .../main/java/io/appwrite/services/Apps.kt | 881 ++++++++++++++ .../main/java/io/appwrite/services/Avatars.kt | 83 +- .../java/io/appwrite/services/Databases.kt | 6 +- .../java/io/appwrite/services/DocumentsDB.kt | 6 +- .../java/io/appwrite/services/Functions.kt | 3 +- .../main/java/io/appwrite/services/Graphql.kt | 3 +- .../main/java/io/appwrite/services/Locale.kt | 73 +- .../java/io/appwrite/services/Messaging.kt | 3 +- .../main/java/io/appwrite/services/Oauth2.kt | 680 +++++++++++ .../java/io/appwrite/services/Organization.kt | 3 +- .../java/io/appwrite/services/Presences.kt | 3 +- .../main/java/io/appwrite/services/Push.kt | 765 +++++++++++++ .../io/appwrite/services/PushBackground.kt | 1009 +++++++++++++++++ .../java/io/appwrite/services/PushCore.kt | 374 ++++++ .../java/io/appwrite/services/PushReceiver.kt | 33 + .../java/io/appwrite/services/PushService.kt | 67 ++ .../java/io/appwrite/services/PushWakeups.kt | 46 + .../java/io/appwrite/services/Realtime.kt | 16 +- .../main/java/io/appwrite/services/Storage.kt | 9 +- .../java/io/appwrite/services/TablesDB.kt | 6 +- .../main/java/io/appwrite/services/Teams.kt | 7 +- .../java/io/appwrite/services/VectorsDB.kt | 6 +- .../io/appwrite/ClientJavaInteropTest.java | 9 + .../src/test/java/io/appwrite/TopicTest.kt | 53 + 210 files changed, 7987 insertions(+), 239 deletions(-) create mode 100644 docs/examples/java/apps/create-installation-token.md create mode 100644 docs/examples/java/apps/create-key.md create mode 100644 docs/examples/java/apps/create-secret.md create mode 100644 docs/examples/java/apps/create.md create mode 100644 docs/examples/java/apps/delete-installation.md create mode 100644 docs/examples/java/apps/delete-key.md create mode 100644 docs/examples/java/apps/delete-secret.md create mode 100644 docs/examples/java/apps/delete-tokens.md create mode 100644 docs/examples/java/apps/delete.md create mode 100644 docs/examples/java/apps/get-installation.md create mode 100644 docs/examples/java/apps/get-key.md create mode 100644 docs/examples/java/apps/get-secret.md create mode 100644 docs/examples/java/apps/get.md create mode 100644 docs/examples/java/apps/list-installation-scopes.md create mode 100644 docs/examples/java/apps/list-installations.md create mode 100644 docs/examples/java/apps/list-keys.md create mode 100644 docs/examples/java/apps/list-o-auth-2-scopes.md create mode 100644 docs/examples/java/apps/list-secrets.md create mode 100644 docs/examples/java/apps/list.md create mode 100644 docs/examples/java/apps/update-team.md create mode 100644 docs/examples/java/apps/update.md create mode 100644 docs/examples/java/avatars/delete-photo.md create mode 100644 docs/examples/java/avatars/update-photo.md create mode 100644 docs/examples/java/oauth2/approve.md create mode 100644 docs/examples/java/oauth2/authorize-post.md create mode 100644 docs/examples/java/oauth2/authorize.md create mode 100644 docs/examples/java/oauth2/create-device-authorization.md create mode 100644 docs/examples/java/oauth2/create-grant.md create mode 100644 docs/examples/java/oauth2/create-par.md create mode 100644 docs/examples/java/oauth2/create-token.md create mode 100644 docs/examples/java/oauth2/get-grant.md create mode 100644 docs/examples/java/oauth2/list-organizations.md create mode 100644 docs/examples/java/oauth2/list-projects.md create mode 100644 docs/examples/java/oauth2/logout-post.md create mode 100644 docs/examples/java/oauth2/logout.md create mode 100644 docs/examples/java/oauth2/reject.md create mode 100644 docs/examples/java/oauth2/revoke.md create mode 100644 docs/examples/kotlin/apps/create-installation-token.md create mode 100644 docs/examples/kotlin/apps/create-key.md create mode 100644 docs/examples/kotlin/apps/create-secret.md create mode 100644 docs/examples/kotlin/apps/create.md create mode 100644 docs/examples/kotlin/apps/delete-installation.md create mode 100644 docs/examples/kotlin/apps/delete-key.md create mode 100644 docs/examples/kotlin/apps/delete-secret.md create mode 100644 docs/examples/kotlin/apps/delete-tokens.md create mode 100644 docs/examples/kotlin/apps/delete.md create mode 100644 docs/examples/kotlin/apps/get-installation.md create mode 100644 docs/examples/kotlin/apps/get-key.md create mode 100644 docs/examples/kotlin/apps/get-secret.md create mode 100644 docs/examples/kotlin/apps/get.md create mode 100644 docs/examples/kotlin/apps/list-installation-scopes.md create mode 100644 docs/examples/kotlin/apps/list-installations.md create mode 100644 docs/examples/kotlin/apps/list-keys.md create mode 100644 docs/examples/kotlin/apps/list-o-auth-2-scopes.md create mode 100644 docs/examples/kotlin/apps/list-secrets.md create mode 100644 docs/examples/kotlin/apps/list.md create mode 100644 docs/examples/kotlin/apps/update-team.md create mode 100644 docs/examples/kotlin/apps/update.md create mode 100644 docs/examples/kotlin/avatars/delete-photo.md create mode 100644 docs/examples/kotlin/avatars/update-photo.md create mode 100644 docs/examples/kotlin/oauth2/approve.md create mode 100644 docs/examples/kotlin/oauth2/authorize-post.md create mode 100644 docs/examples/kotlin/oauth2/authorize.md create mode 100644 docs/examples/kotlin/oauth2/create-device-authorization.md create mode 100644 docs/examples/kotlin/oauth2/create-grant.md create mode 100644 docs/examples/kotlin/oauth2/create-par.md create mode 100644 docs/examples/kotlin/oauth2/create-token.md create mode 100644 docs/examples/kotlin/oauth2/get-grant.md create mode 100644 docs/examples/kotlin/oauth2/list-organizations.md create mode 100644 docs/examples/kotlin/oauth2/list-projects.md create mode 100644 docs/examples/kotlin/oauth2/logout-post.md create mode 100644 docs/examples/kotlin/oauth2/logout.md create mode 100644 docs/examples/kotlin/oauth2/reject.md create mode 100644 docs/examples/kotlin/oauth2/revoke.md create mode 100644 library/consumer-rules.pro create mode 100644 library/src/main/java/io/appwrite/Topic.kt create mode 100644 library/src/main/java/io/appwrite/models/Account.kt create mode 100644 library/src/main/java/io/appwrite/models/App.kt create mode 100644 library/src/main/java/io/appwrite/models/AppKey.kt create mode 100644 library/src/main/java/io/appwrite/models/AppKeyList.kt create mode 100644 library/src/main/java/io/appwrite/models/AppScope.kt create mode 100644 library/src/main/java/io/appwrite/models/AppScopeList.kt create mode 100644 library/src/main/java/io/appwrite/models/AppSecret.kt create mode 100644 library/src/main/java/io/appwrite/models/AppSecretList.kt create mode 100644 library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt create mode 100644 library/src/main/java/io/appwrite/models/AppsList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Approve.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Authorize.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Grant.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Organization.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2PAR.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Project.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Reject.kt create mode 100644 library/src/main/java/io/appwrite/models/Oauth2Token.kt create mode 100644 library/src/main/java/io/appwrite/services/Apps.kt create mode 100644 library/src/main/java/io/appwrite/services/Oauth2.kt create mode 100644 library/src/main/java/io/appwrite/services/Push.kt create mode 100644 library/src/main/java/io/appwrite/services/PushBackground.kt create mode 100644 library/src/main/java/io/appwrite/services/PushCore.kt create mode 100644 library/src/main/java/io/appwrite/services/PushReceiver.kt create mode 100644 library/src/main/java/io/appwrite/services/PushService.kt create mode 100644 library/src/main/java/io/appwrite/services/PushWakeups.kt create mode 100644 library/src/test/java/io/appwrite/TopicTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 435676ce..bef16837 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Change Log +## 29.0.0 + +* Breaking: minimum Android SDK raised from 23 to 24 (Android 7.0) +* Added: `Push` service for MQTT push messages without FCM, plus `PushReceiver` for background handling +* Added: `Topic` builder for push topics +* Added: `endpointPush` and `pushClientId` on `Client`, with `setPushEndpoint` and `setPushClientId` +* Added: `Apps` service for managing OAuth2 apps, keys, secrets and installations +* Added: `Oauth2` service for using Appwrite as an OAuth2/OIDC provider +* Added: `Avatars.updatePhoto` and `Avatars.deletePhoto` +* Added: `duration` parameter to `Account.createEmailPasswordSession` +* Added: `current` parameter to `Account.deleteSessions` +* Added: `total` parameter to `Account.listSessions`, `Locale` list methods and `listTransactions` +* Added: `WEBFLOW` value to `OAuthProvider` +* Added: `toMap()` and `toJson()` on `Query` and `Operator` +* Updated: SDK manifest merges Push permissions, a foreground service and a boot receiver into apps +* Updated: narrower Netty keep rules, raising the R8 obfuscation and optimization scores Google Play reads +* Updated: added the shaded `hivemq-mqtt-client` dependency for `Push`, with consumer R8 rules +* Fixed: `Query` and `Operator` serialize correctly under R8 instead of producing `{}` +* Fixed: Realtime delivers events and error messages under R8 +* Fixed: session cookies saved under R8 stay readable after an app update +* Fixed: `ID.unique()` and `InputFile.fromFile` no longer crash on Android 7.x +* Fixed: Kotlin and Java examples now compile +* Fixed: unsubscribing one of several subscriptions on a topic stops its callback + ## 28.0.0 * Breaking: removed `Account.listLogs` and the `Log`, `LogList` models diff --git a/README.md b/README.md index c8fb25f0..aeffd888 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:28.0.0") +implementation("io.appwrite:sdk-for-android:29.0.0") ``` ### Maven @@ -49,11 +49,52 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 28.0.0 + 29.0.0 ``` +### Code shrinking (R8) + +The SDK ships the R8 rules it needs, so no extra configuration is required. Your own classes +that the SDK fills from JSON (the `nestedType` of documents, rows and preferences, and the +`payloadType` of Realtime subscriptions) are read by Gson through reflection. Annotate them +with `@Keep` so R8 keeps their fields: + +```kotlin +@Keep +data class Note(val title: String, val body: String) +``` + +### Push notifications + +#### Background delivery + +A subscription with `background = true` keeps delivering after the app is backgrounded, its +process is killed, or the device restarts, until it is unsubscribed or `push.close()` is +called (do this on sign-out). No service is kept running: a scheduled job and alarm wake the +app every 15 to 60 seconds to reconnect, and the broker replays what was sent in between +(`retry = true`). It reconnects with the credential saved at subscribe time, so use a session +(`client.setSession(...)`), not a short-lived JWT. + +When no in-app callback is listening, each message is posted as a notification that opens +your launch activity with `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` in its extras. Set its +icon with `` +in your ``. To handle these messages in code, subclass `PushReceiver` and declare +it with the `io.appwrite.push.MESSAGE` action (see its docs). + +Doze delays the scheduled runs. They are more punctual when your app may schedule exact +alarms (`SCHEDULE_EXACT_ALARM`) or is exempt from battery optimisation; both are your app's +choice and subject to Google Play policy, and the SDK uses them when granted. For immediate delivery even after a kill and +during Doze, call `push.setForeground(true)` from the foreground: it adds a foreground service +with a quiet ongoing notification. If you never enable it, you can remove the service from +your merged manifest, so the Play Console does not ask you to declare it: + +```xml + + +``` + ## Getting Started diff --git a/docs/examples/java/account/create-email-password-session.md b/docs/examples/java/account/create-email-password-session.md index fa7d2523..9e254bd2 100644 --- a/docs/examples/java/account/create-email-password-session.md +++ b/docs/examples/java/account/create-email-password-session.md @@ -14,6 +14,7 @@ Account account = new Account(client); account.createEmailPasswordSession( "email@example.com", // email "password", // password + 60L, // duration (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/account/create-id-token-session.md b/docs/examples/java/account/create-id-token-session.md index 457b9fee..b7bd83fc 100644 --- a/docs/examples/java/account/create-id-token-session.md +++ b/docs/examples/java/account/create-id-token-session.md @@ -17,7 +17,7 @@ account.createIdTokenSession( "", // idToken "", // nonce (optional) "", // accessToken (optional) - 0, // accessTokenExpiry (optional) + 0L, // accessTokenExpiry (optional) "", // name (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/account/create-jwt.md b/docs/examples/java/account/create-jwt.md index f405d306..c41c5721 100644 --- a/docs/examples/java/account/create-jwt.md +++ b/docs/examples/java/account/create-jwt.md @@ -12,7 +12,7 @@ Client client = new Client(context) Account account = new Account(client); account.createJWT( - 0, // duration (optional) + 0L, // duration (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/account/delete-sessions.md b/docs/examples/java/account/delete-sessions.md index 4b93d19a..e0dce6ca 100644 --- a/docs/examples/java/account/delete-sessions.md +++ b/docs/examples/java/account/delete-sessions.md @@ -11,12 +11,16 @@ Client client = new Client(context) Account account = new Account(client); -account.deleteSessions(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +account.deleteSessions( + false, // current (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/account/list-sessions.md b/docs/examples/java/account/list-sessions.md index 9e52ea47..5ea50625 100644 --- a/docs/examples/java/account/list-sessions.md +++ b/docs/examples/java/account/list-sessions.md @@ -11,12 +11,16 @@ Client client = new Client(context) Account account = new Account(client); -account.listSessions(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +account.listSessions( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/apps/create-installation-token.md b/docs/examples/java/apps/create-installation-token.md new file mode 100644 index 00000000..e46fc2af --- /dev/null +++ b/docs/examples/java/apps/create-installation-token.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.createInstallationToken( + "", // appId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/create-key.md b/docs/examples/java/apps/create-key.md new file mode 100644 index 00000000..729c98fd --- /dev/null +++ b/docs/examples/java/apps/create-key.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.createKey( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/create-secret.md b/docs/examples/java/apps/create-secret.md new file mode 100644 index 00000000..8acb44ef --- /dev/null +++ b/docs/examples/java/apps/create-secret.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.createSecret( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/create.md b/docs/examples/java/apps/create.md new file mode 100644 index 00000000..88694a94 --- /dev/null +++ b/docs/examples/java/apps/create.md @@ -0,0 +1,44 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.create( + "", // appId + "", // name + List.of(), // redirectUris + "", // description (optional) + "https://example.com", // clientUri (optional) + "https://example.com", // logoUri (optional) + "https://example.com", // privacyPolicyUrl (optional) + "https://example.com", // termsUrl (optional) + List.of(), // contacts (optional) + "", // tagline (optional) + List.of(), // tags (optional) + List.of(), // images (optional) + "https://example.com", // supportUrl (optional) + "https://example.com", // dataDeletionUrl (optional) + List.of(), // postLogoutRedirectUris (optional) + false, // enabled (optional) + "public", // type (optional) + false, // deviceFlow (optional) + "", // teamId (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-installation.md b/docs/examples/java/apps/delete-installation.md new file mode 100644 index 00000000..559ab654 --- /dev/null +++ b/docs/examples/java/apps/delete-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteInstallation( + "", // appId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-key.md b/docs/examples/java/apps/delete-key.md new file mode 100644 index 00000000..312e0be0 --- /dev/null +++ b/docs/examples/java/apps/delete-key.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteKey( + "", // appId + "", // keyId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-secret.md b/docs/examples/java/apps/delete-secret.md new file mode 100644 index 00000000..b21fc759 --- /dev/null +++ b/docs/examples/java/apps/delete-secret.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteSecret( + "", // appId + "", // secretId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete-tokens.md b/docs/examples/java/apps/delete-tokens.md new file mode 100644 index 00000000..f873873e --- /dev/null +++ b/docs/examples/java/apps/delete-tokens.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.deleteTokens( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/delete.md b/docs/examples/java/apps/delete.md new file mode 100644 index 00000000..0c99520c --- /dev/null +++ b/docs/examples/java/apps/delete.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.delete( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get-installation.md b/docs/examples/java/apps/get-installation.md new file mode 100644 index 00000000..f8cf6b56 --- /dev/null +++ b/docs/examples/java/apps/get-installation.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.getInstallation( + "", // appId + "", // installationId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get-key.md b/docs/examples/java/apps/get-key.md new file mode 100644 index 00000000..729ccfa6 --- /dev/null +++ b/docs/examples/java/apps/get-key.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.getKey( + "", // appId + "", // keyId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get-secret.md b/docs/examples/java/apps/get-secret.md new file mode 100644 index 00000000..f4f3c68f --- /dev/null +++ b/docs/examples/java/apps/get-secret.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.getSecret( + "", // appId + "", // secretId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/get.md b/docs/examples/java/apps/get.md new file mode 100644 index 00000000..0b4c5378 --- /dev/null +++ b/docs/examples/java/apps/get.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.get( + "", // appId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list-installation-scopes.md b/docs/examples/java/apps/list-installation-scopes.md new file mode 100644 index 00000000..c09da789 --- /dev/null +++ b/docs/examples/java/apps/list-installation-scopes.md @@ -0,0 +1,22 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listInstallationScopes(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); +})); +``` diff --git a/docs/examples/java/apps/list-installations.md b/docs/examples/java/apps/list-installations.md new file mode 100644 index 00000000..f83097e1 --- /dev/null +++ b/docs/examples/java/apps/list-installations.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listInstallations( + "", // appId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list-keys.md b/docs/examples/java/apps/list-keys.md new file mode 100644 index 00000000..7a872751 --- /dev/null +++ b/docs/examples/java/apps/list-keys.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listKeys( + "", // appId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list-o-auth-2-scopes.md b/docs/examples/java/apps/list-o-auth-2-scopes.md new file mode 100644 index 00000000..d96f8c36 --- /dev/null +++ b/docs/examples/java/apps/list-o-auth-2-scopes.md @@ -0,0 +1,22 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listOAuth2Scopes(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); +})); +``` diff --git a/docs/examples/java/apps/list-secrets.md b/docs/examples/java/apps/list-secrets.md new file mode 100644 index 00000000..4d823a74 --- /dev/null +++ b/docs/examples/java/apps/list-secrets.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.listSecrets( + "", // appId + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/list.md b/docs/examples/java/apps/list.md new file mode 100644 index 00000000..31d80daa --- /dev/null +++ b/docs/examples/java/apps/list.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.list( + List.of(), // queries (optional) + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/update-team.md b/docs/examples/java/apps/update-team.md new file mode 100644 index 00000000..314c672f --- /dev/null +++ b/docs/examples/java/apps/update-team.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.updateTeam( + "", // appId + "", // teamId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/apps/update.md b/docs/examples/java/apps/update.md new file mode 100644 index 00000000..e8b51fa9 --- /dev/null +++ b/docs/examples/java/apps/update.md @@ -0,0 +1,45 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Apps; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Apps apps = new Apps(client); + +apps.update( + "", // appId + "", // name + "", // description (optional) + "https://example.com", // clientUri (optional) + "https://example.com", // logoUri (optional) + "https://example.com", // privacyPolicyUrl (optional) + "https://example.com", // termsUrl (optional) + List.of(), // contacts (optional) + "", // tagline (optional) + List.of(), // tags (optional) + List.of(), // images (optional) + "https://example.com", // supportUrl (optional) + "https://example.com", // dataDeletionUrl (optional) + false, // enabled (optional) + List.of(), // redirectUris (optional) + List.of(), // postLogoutRedirectUris (optional) + "public", // type (optional) + false, // deviceFlow (optional) + List.of(), // installationScopes (optional) + "https://example.com", // installationRedirectUrl (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/avatars/delete-photo.md b/docs/examples/java/avatars/delete-photo.md new file mode 100644 index 00000000..cffc010c --- /dev/null +++ b/docs/examples/java/avatars/delete-photo.md @@ -0,0 +1,22 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Avatars; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Avatars avatars = new Avatars(client); + +avatars.deletePhoto(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); +})); +``` diff --git a/docs/examples/java/avatars/get-browser.md b/docs/examples/java/avatars/get-browser.md index f101ad54..626de034 100644 --- a/docs/examples/java/avatars/get-browser.md +++ b/docs/examples/java/avatars/get-browser.md @@ -14,9 +14,9 @@ Avatars avatars = new Avatars(client); avatars.getBrowser( Browser.AVANT_BROWSER, // code - 0, // width (optional) - 0, // height (optional) - -1, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + -1L, // quality (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-credit-card.md b/docs/examples/java/avatars/get-credit-card.md index b12cebc9..f8e114db 100644 --- a/docs/examples/java/avatars/get-credit-card.md +++ b/docs/examples/java/avatars/get-credit-card.md @@ -14,9 +14,9 @@ Avatars avatars = new Avatars(client); avatars.getCreditCard( CreditCard.AMERICAN_EXPRESS, // code - 0, // width (optional) - 0, // height (optional) - -1, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + -1L, // quality (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-flag.md b/docs/examples/java/avatars/get-flag.md index 4d808513..0acd391f 100644 --- a/docs/examples/java/avatars/get-flag.md +++ b/docs/examples/java/avatars/get-flag.md @@ -14,9 +14,9 @@ Avatars avatars = new Avatars(client); avatars.getFlag( Flag.AFGHANISTAN, // code - 0, // width (optional) - 0, // height (optional) - -1, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + -1L, // quality (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-image.md b/docs/examples/java/avatars/get-image.md index a22041f5..38bd4dc8 100644 --- a/docs/examples/java/avatars/get-image.md +++ b/docs/examples/java/avatars/get-image.md @@ -13,8 +13,8 @@ Avatars avatars = new Avatars(client); avatars.getImage( "https://example.com", // url - 0, // width (optional) - 0, // height (optional) + 0L, // width (optional) + 0L, // height (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/avatars/get-initials.md b/docs/examples/java/avatars/get-initials.md index 08f5bea9..c22358d2 100644 --- a/docs/examples/java/avatars/get-initials.md +++ b/docs/examples/java/avatars/get-initials.md @@ -13,8 +13,8 @@ Avatars avatars = new Avatars(client); avatars.getInitials( "", // name (optional) - 0, // width (optional) - 0, // height (optional) + 0L, // width (optional) + 0L, // height (optional) "FFFFFF", // background (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/avatars/get-photo.md b/docs/examples/java/avatars/get-photo.md index 2204db78..04769d1c 100644 --- a/docs/examples/java/avatars/get-photo.md +++ b/docs/examples/java/avatars/get-photo.md @@ -12,9 +12,9 @@ Client client = new Client(context) Avatars avatars = new Avatars(client); avatars.getPhoto( - 0, // width (optional) - 0, // height (optional) - 0, // quality (optional) + 0L, // width (optional) + 0L, // height (optional) + 0L, // quality (optional) "png", // output (optional) "g", // rating (optional) "current()", // userId (optional) diff --git a/docs/examples/java/avatars/get-qr.md b/docs/examples/java/avatars/get-qr.md index ad08ab56..b3bbb6b6 100644 --- a/docs/examples/java/avatars/get-qr.md +++ b/docs/examples/java/avatars/get-qr.md @@ -13,8 +13,8 @@ Avatars avatars = new Avatars(client); avatars.getQR( "", // text - 1, // size (optional) - 0, // margin (optional) + 1L, // size (optional) + 0L, // margin (optional) false, // download (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/avatars/get-screenshot.md b/docs/examples/java/avatars/get-screenshot.md index 71f96c5b..82454a65 100644 --- a/docs/examples/java/avatars/get-screenshot.md +++ b/docs/examples/java/avatars/get-screenshot.md @@ -18,12 +18,11 @@ Avatars avatars = new Avatars(client); avatars.getScreenshot( "https://example.com", // url Map.of( - "Authorization", "Bearer token123", - "X-Custom-Header", "value" + "Accept-Language", "en-US,en;q=0.9" ), // headers (optional) - 1920, // viewportWidth (optional) - 1080, // viewportHeight (optional) - 2, // scale (optional) + 1920L, // viewportWidth (optional) + 1080L, // viewportHeight (optional) + 2.0, // scale (optional) BrowserTheme.LIGHT, // theme (optional) "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15", // userAgent (optional) true, // fullpage (optional) @@ -31,13 +30,13 @@ avatars.getScreenshot( Timezone.AFRICA_ABIDJAN, // timezone (optional) 37.7749, // latitude (optional) -122.4194, // longitude (optional) - 100, // accuracy (optional) + 100.0, // accuracy (optional) true, // touch (optional) BrowserPermission.GEOLOCATION, // permissions (optional) - 3, // sleep (optional) - 800, // width (optional) - 600, // height (optional) - 85, // quality (optional) + 3L, // sleep (optional) + 800L, // width (optional) + 600L, // height (optional) + 85L, // quality (optional) ImageFormat.JPG, // output (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/avatars/update-photo.md b/docs/examples/java/avatars/update-photo.md new file mode 100644 index 00000000..8c4d6e90 --- /dev/null +++ b/docs/examples/java/avatars/update-photo.md @@ -0,0 +1,27 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.models.InputFile; +import io.appwrite.services.Avatars; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Avatars avatars = new Avatars(client); + +avatars.updatePhoto( + InputFile.fromPath("file.png"), // file + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/databases/create-transaction.md b/docs/examples/java/databases/create-transaction.md index d96c27a0..616838d2 100644 --- a/docs/examples/java/databases/create-transaction.md +++ b/docs/examples/java/databases/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) Databases databases = new Databases(client); databases.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/databases/decrement-document-attribute.md b/docs/examples/java/databases/decrement-document-attribute.md index 1a2f23e2..682dadd2 100644 --- a/docs/examples/java/databases/decrement-document-attribute.md +++ b/docs/examples/java/databases/decrement-document-attribute.md @@ -16,8 +16,8 @@ databases.decrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 0, // min (optional) + 1.0, // value (optional) + 0.0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/databases/increment-document-attribute.md b/docs/examples/java/databases/increment-document-attribute.md index fe363592..07dfd55c 100644 --- a/docs/examples/java/databases/increment-document-attribute.md +++ b/docs/examples/java/databases/increment-document-attribute.md @@ -16,8 +16,8 @@ databases.incrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 100, // max (optional) + 1.0, // value (optional) + 100.0, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/databases/list-documents.md b/docs/examples/java/databases/list-documents.md index 8569cb60..8a5a52cb 100644 --- a/docs/examples/java/databases/list-documents.md +++ b/docs/examples/java/databases/list-documents.md @@ -17,7 +17,7 @@ databases.listDocuments( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/databases/list-transactions.md b/docs/examples/java/databases/list-transactions.md index ad11c7f7..3e7ca138 100644 --- a/docs/examples/java/databases/list-transactions.md +++ b/docs/examples/java/databases/list-transactions.md @@ -13,6 +13,7 @@ Databases databases = new Databases(client); databases.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/documentsdb/create-documents.md b/docs/examples/java/documentsdb/create-documents.md index c52433a2..ae20335d 100644 --- a/docs/examples/java/documentsdb/create-documents.md +++ b/docs/examples/java/documentsdb/create-documents.md @@ -14,7 +14,14 @@ DocumentsDB documentsDB = new DocumentsDB(client); documentsDB.createDocuments( "", // databaseId "", // collectionId - List.of(), // documents + List.of(Map.of( + "$id", "example1", + "username", "walter.obrien", + "email", "walter.obrien@example.com", + "fullName", "Walter O'Brien", + "age", 30, + "isAdmin", false + )), // documents "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/documentsdb/create-transaction.md b/docs/examples/java/documentsdb/create-transaction.md index da91c5c8..ac4a96d8 100644 --- a/docs/examples/java/documentsdb/create-transaction.md +++ b/docs/examples/java/documentsdb/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) DocumentsDB documentsDB = new DocumentsDB(client); documentsDB.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/documentsdb/decrement-document-attribute.md b/docs/examples/java/documentsdb/decrement-document-attribute.md index f1853ac6..9e19095e 100644 --- a/docs/examples/java/documentsdb/decrement-document-attribute.md +++ b/docs/examples/java/documentsdb/decrement-document-attribute.md @@ -16,8 +16,8 @@ documentsDB.decrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 0, // min (optional) + 1.0, // value (optional) + 0.0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/documentsdb/increment-document-attribute.md b/docs/examples/java/documentsdb/increment-document-attribute.md index fab214e9..470abbf8 100644 --- a/docs/examples/java/documentsdb/increment-document-attribute.md +++ b/docs/examples/java/documentsdb/increment-document-attribute.md @@ -16,8 +16,8 @@ documentsDB.incrementDocumentAttribute( "", // collectionId "", // documentId "", // attribute - 1, // value (optional) - 100, // max (optional) + 1.0, // value (optional) + 100.0, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/documentsdb/list-documents.md b/docs/examples/java/documentsdb/list-documents.md index 0abb6734..f6cfe483 100644 --- a/docs/examples/java/documentsdb/list-documents.md +++ b/docs/examples/java/documentsdb/list-documents.md @@ -17,7 +17,7 @@ documentsDB.listDocuments( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/documentsdb/list-transactions.md b/docs/examples/java/documentsdb/list-transactions.md index 9b8fcd7b..ab4974a0 100644 --- a/docs/examples/java/documentsdb/list-transactions.md +++ b/docs/examples/java/documentsdb/list-transactions.md @@ -13,6 +13,7 @@ DocumentsDB documentsDB = new DocumentsDB(client); documentsDB.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/graphql/mutation.md b/docs/examples/java/graphql/mutation.md index 9cc7febe..a8488db1 100644 --- a/docs/examples/java/graphql/mutation.md +++ b/docs/examples/java/graphql/mutation.md @@ -12,7 +12,9 @@ Client client = new Client(context) Graphql graphql = new Graphql(client); graphql.mutation( - Map.of("a", "b"), // query + Map.of( + "query", "mutation { accountUpdateName(name: \"Walter\") { name } }" + ), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/graphql/query.md b/docs/examples/java/graphql/query.md index 25704b00..58e63635 100644 --- a/docs/examples/java/graphql/query.md +++ b/docs/examples/java/graphql/query.md @@ -12,7 +12,9 @@ Client client = new Client(context) Graphql graphql = new Graphql(client); graphql.query( - Map.of("a", "b"), // query + Map.of( + "query", "query { localeGet { ip } }" + ), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/locale/list-codes.md b/docs/examples/java/locale/list-codes.md index 534b1199..181c1b8f 100644 --- a/docs/examples/java/locale/list-codes.md +++ b/docs/examples/java/locale/list-codes.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCodes(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCodes( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-continents.md b/docs/examples/java/locale/list-continents.md index 998a31be..e8a60fca 100644 --- a/docs/examples/java/locale/list-continents.md +++ b/docs/examples/java/locale/list-continents.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listContinents(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listContinents( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-countries-eu.md b/docs/examples/java/locale/list-countries-eu.md index 5950c990..b6c878fc 100644 --- a/docs/examples/java/locale/list-countries-eu.md +++ b/docs/examples/java/locale/list-countries-eu.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCountriesEU(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCountriesEU( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-countries-phones.md b/docs/examples/java/locale/list-countries-phones.md index 6096758e..3d8bb9eb 100644 --- a/docs/examples/java/locale/list-countries-phones.md +++ b/docs/examples/java/locale/list-countries-phones.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCountriesPhones(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCountriesPhones( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-countries.md b/docs/examples/java/locale/list-countries.md index 140fc48a..3238cf27 100644 --- a/docs/examples/java/locale/list-countries.md +++ b/docs/examples/java/locale/list-countries.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCountries(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCountries( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-currencies.md b/docs/examples/java/locale/list-currencies.md index 0e5273d9..631bb346 100644 --- a/docs/examples/java/locale/list-currencies.md +++ b/docs/examples/java/locale/list-currencies.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listCurrencies(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listCurrencies( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/locale/list-languages.md b/docs/examples/java/locale/list-languages.md index e85bd0a1..b0000633 100644 --- a/docs/examples/java/locale/list-languages.md +++ b/docs/examples/java/locale/list-languages.md @@ -11,12 +11,16 @@ Client client = new Client(context) Locale locale = new Locale(client); -locale.listLanguages(new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +locale.listLanguages( + false, // total (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); - Log.d("Appwrite", result.toString()); -})); ``` diff --git a/docs/examples/java/oauth2/approve.md b/docs/examples/java/oauth2/approve.md new file mode 100644 index 00000000..f2f4a495 --- /dev/null +++ b/docs/examples/java/oauth2/approve.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.approve( + "", // grant_id + "", // authorization_details (optional) + "", // scope (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/authorize-post.md b/docs/examples/java/oauth2/authorize-post.md new file mode 100644 index 00000000..99ed6b8e --- /dev/null +++ b/docs/examples/java/oauth2/authorize-post.md @@ -0,0 +1,39 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.authorizePost( + "", // client_id (optional) + "https://example.com", // redirect_uri (optional) + "", // response_type (optional) + "", // scope (optional) + "", // state (optional) + "", // nonce (optional) + "", // code_challenge (optional) + "s256", // code_challenge_method (optional) + "", // prompt (optional) + 0L, // max_age (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + "", // request_uri (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/authorize.md b/docs/examples/java/oauth2/authorize.md new file mode 100644 index 00000000..62a452c8 --- /dev/null +++ b/docs/examples/java/oauth2/authorize.md @@ -0,0 +1,39 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.authorize( + "", // client_id (optional) + "https://example.com", // redirect_uri (optional) + "", // response_type (optional) + "", // scope (optional) + "", // state (optional) + "", // nonce (optional) + "", // code_challenge (optional) + "s256", // code_challenge_method (optional) + "", // prompt (optional) + 0L, // max_age (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + "", // request_uri (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-device-authorization.md b/docs/examples/java/oauth2/create-device-authorization.md new file mode 100644 index 00000000..0b65a1ab --- /dev/null +++ b/docs/examples/java/oauth2/create-device-authorization.md @@ -0,0 +1,30 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createDeviceAuthorization( + "", // client_id (optional) + "", // scope (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-grant.md b/docs/examples/java/oauth2/create-grant.md new file mode 100644 index 00000000..a14c3886 --- /dev/null +++ b/docs/examples/java/oauth2/create-grant.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createGrant( + "", // user_code + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-par.md b/docs/examples/java/oauth2/create-par.md new file mode 100644 index 00000000..e65bdb54 --- /dev/null +++ b/docs/examples/java/oauth2/create-par.md @@ -0,0 +1,38 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createPAR( + "", // client_id + "https://example.com", // redirect_uri + "code", // response_type + "", // scope (optional) + "", // state (optional) + "", // nonce (optional) + "", // code_challenge (optional) + "s256", // code_challenge_method (optional) + "", // prompt (optional) + 0L, // max_age (optional) + "", // authorization_details (optional) + "", // resource (optional) + "", // audience (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/create-token.md b/docs/examples/java/oauth2/create-token.md new file mode 100644 index 00000000..39b6fa25 --- /dev/null +++ b/docs/examples/java/oauth2/create-token.md @@ -0,0 +1,35 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.createToken( + "", // grant_type + "", // code (optional) + "", // refresh_token (optional) + "", // device_code (optional) + "", // client_id (optional) + "", // client_secret (optional) + "", // code_verifier (optional) + "https://example.com", // redirect_uri (optional) + "", // resource (optional) + "", // audience (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/get-grant.md b/docs/examples/java/oauth2/get-grant.md new file mode 100644 index 00000000..18068889 --- /dev/null +++ b/docs/examples/java/oauth2/get-grant.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.getGrant( + "", // grant_id + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/list-organizations.md b/docs/examples/java/oauth2/list-organizations.md new file mode 100644 index 00000000..bbc74a78 --- /dev/null +++ b/docs/examples/java/oauth2/list-organizations.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.listOrganizations( + 1L, // limit (optional) + 0L, // offset (optional) + "", // search (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/list-projects.md b/docs/examples/java/oauth2/list-projects.md new file mode 100644 index 00000000..6963583a --- /dev/null +++ b/docs/examples/java/oauth2/list-projects.md @@ -0,0 +1,28 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.listProjects( + 1L, // limit (optional) + 0L, // offset (optional) + "", // search (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/logout-post.md b/docs/examples/java/oauth2/logout-post.md new file mode 100644 index 00000000..fefd5e92 --- /dev/null +++ b/docs/examples/java/oauth2/logout-post.md @@ -0,0 +1,31 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.logoutPost( + "", // id_token_hint (optional) + "", // logout_hint (optional) + "", // client_id (optional) + "https://example.com", // post_logout_redirect_uri (optional) + "", // state (optional) + "", // ui_locales (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/logout.md b/docs/examples/java/oauth2/logout.md new file mode 100644 index 00000000..ca7739a2 --- /dev/null +++ b/docs/examples/java/oauth2/logout.md @@ -0,0 +1,31 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.logout( + "", // id_token_hint (optional) + "", // logout_hint (optional) + "", // client_id (optional) + "https://example.com", // post_logout_redirect_uri (optional) + "", // state (optional) + "", // ui_locales (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/reject.md b/docs/examples/java/oauth2/reject.md new file mode 100644 index 00000000..90b9b2e2 --- /dev/null +++ b/docs/examples/java/oauth2/reject.md @@ -0,0 +1,26 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.reject( + "", // grant_id + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/oauth2/revoke.md b/docs/examples/java/oauth2/revoke.md new file mode 100644 index 00000000..0d680eb1 --- /dev/null +++ b/docs/examples/java/oauth2/revoke.md @@ -0,0 +1,29 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Oauth2; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Oauth2 oauth2 = new Oauth2(client); + +oauth2.revoke( + "", // token + "access_token", // token_type_hint (optional) + "", // client_id (optional) + "", // client_secret (optional) + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/presences/list.md b/docs/examples/java/presences/list.md index d66a4161..c2430748 100644 --- a/docs/examples/java/presences/list.md +++ b/docs/examples/java/presences/list.md @@ -14,7 +14,7 @@ Presences presences = new Presences(client); presences.list( List.of(), // queries (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/storage/get-file-preview.md b/docs/examples/java/storage/get-file-preview.md index 3d753c44..2f1060a1 100644 --- a/docs/examples/java/storage/get-file-preview.md +++ b/docs/examples/java/storage/get-file-preview.md @@ -16,15 +16,15 @@ Storage storage = new Storage(client); storage.getFilePreview( "", // bucketId "", // fileId - 0, // width (optional) - 0, // height (optional) + 0L, // width (optional) + 0L, // height (optional) ImageGravity.AUTO, // gravity (optional) - -1, // quality (optional) - 0, // borderWidth (optional) + -1L, // quality (optional) + 0L, // borderWidth (optional) "FFFFFF", // borderColor (optional) - 0, // borderRadius (optional) - 0, // opacity (optional) - -360, // rotation (optional) + 0L, // borderRadius (optional) + 0.0, // opacity (optional) + -360L, // rotation (optional) "FFFFFF", // background (optional) ImageFormat.JPG, // output (optional) "", // token (optional) diff --git a/docs/examples/java/tablesdb/create-transaction.md b/docs/examples/java/tablesdb/create-transaction.md index 8401bfec..0dab2f0c 100644 --- a/docs/examples/java/tablesdb/create-transaction.md +++ b/docs/examples/java/tablesdb/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) TablesDB tablesDB = new TablesDB(client); tablesDB.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/tablesdb/decrement-row-column.md b/docs/examples/java/tablesdb/decrement-row-column.md index c998b442..2315f165 100644 --- a/docs/examples/java/tablesdb/decrement-row-column.md +++ b/docs/examples/java/tablesdb/decrement-row-column.md @@ -16,8 +16,8 @@ tablesDB.decrementRowColumn( "", // tableId "", // rowId "", // column - 1, // value (optional) - 0, // min (optional) + 1.0, // value (optional) + 0.0, // min (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/tablesdb/increment-row-column.md b/docs/examples/java/tablesdb/increment-row-column.md index 5fb7189e..2e9b52ba 100644 --- a/docs/examples/java/tablesdb/increment-row-column.md +++ b/docs/examples/java/tablesdb/increment-row-column.md @@ -16,8 +16,8 @@ tablesDB.incrementRowColumn( "", // tableId "", // rowId "", // column - 1, // value (optional) - 100, // max (optional) + 1.0, // value (optional) + 100.0, // max (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/tablesdb/list-rows.md b/docs/examples/java/tablesdb/list-rows.md index cbf40287..6d3a0a78 100644 --- a/docs/examples/java/tablesdb/list-rows.md +++ b/docs/examples/java/tablesdb/list-rows.md @@ -17,7 +17,7 @@ tablesDB.listRows( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/tablesdb/list-transactions.md b/docs/examples/java/tablesdb/list-transactions.md index 9ebb180a..8a04ad9c 100644 --- a/docs/examples/java/tablesdb/list-transactions.md +++ b/docs/examples/java/tablesdb/list-transactions.md @@ -13,6 +13,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/teams/create-membership.md b/docs/examples/java/teams/create-membership.md index 861aeb62..d7fba7c1 100644 --- a/docs/examples/java/teams/create-membership.md +++ b/docs/examples/java/teams/create-membership.md @@ -13,7 +13,7 @@ Teams teams = new Teams(client); teams.createMembership( "", // teamId - List.of(), // roles + List.of("editor"), // roles "email@example.com", // email (optional) "", // userId (optional) "+12065550100", // phone (optional) diff --git a/docs/examples/java/teams/update-membership.md b/docs/examples/java/teams/update-membership.md index 60231f2c..c4208a83 100644 --- a/docs/examples/java/teams/update-membership.md +++ b/docs/examples/java/teams/update-membership.md @@ -14,7 +14,7 @@ Teams teams = new Teams(client); teams.updateMembership( "", // teamId "", // membershipId - List.of(), // roles + List.of("editor"), // roles new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/create-operations.md b/docs/examples/java/vectorsdb/create-operations.md index 7e503634..8dd6f0b8 100644 --- a/docs/examples/java/vectorsdb/create-operations.md +++ b/docs/examples/java/vectorsdb/create-operations.md @@ -19,7 +19,10 @@ vectorsDB.createOperations( "collectionId", "", "documentId", "", "data", Map.of( - "name", "Walter O'Brien" + "embeddings", List.of(0.12, -0.55, 0.88, 1.02), + "metadata", Map.of( + "name", "First document" + ) ) )), // operations (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/java/vectorsdb/create-query.md b/docs/examples/java/vectorsdb/create-query.md index 3f1cfd5c..8c798724 100644 --- a/docs/examples/java/vectorsdb/create-query.md +++ b/docs/examples/java/vectorsdb/create-query.md @@ -17,7 +17,7 @@ vectorsDB.createQuery( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/create-transaction.md b/docs/examples/java/vectorsdb/create-transaction.md index 4784fc47..916f73c1 100644 --- a/docs/examples/java/vectorsdb/create-transaction.md +++ b/docs/examples/java/vectorsdb/create-transaction.md @@ -12,7 +12,7 @@ Client client = new Client(context) VectorsDB vectorsDB = new VectorsDB(client); vectorsDB.createTransaction( - 60, // ttl (optional) + 60L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/list-documents.md b/docs/examples/java/vectorsdb/list-documents.md index 12ef1f0c..49689d36 100644 --- a/docs/examples/java/vectorsdb/list-documents.md +++ b/docs/examples/java/vectorsdb/list-documents.md @@ -17,7 +17,7 @@ vectorsDB.listDocuments( List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) - 0, // ttl (optional) + 0L, // ttl (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/vectorsdb/list-transactions.md b/docs/examples/java/vectorsdb/list-transactions.md index 18ef0e55..be8905e2 100644 --- a/docs/examples/java/vectorsdb/list-transactions.md +++ b/docs/examples/java/vectorsdb/list-transactions.md @@ -13,6 +13,7 @@ VectorsDB vectorsDB = new VectorsDB(client); vectorsDB.listTransactions( List.of(), // queries (optional) + false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/kotlin/account/create-email-password-session.md b/docs/examples/kotlin/account/create-email-password-session.md index 6100e483..aaceb65f 100644 --- a/docs/examples/kotlin/account/create-email-password-session.md +++ b/docs/examples/kotlin/account/create-email-password-session.md @@ -12,5 +12,6 @@ val account = Account(client) val result = account.createEmailPasswordSession( email = "email@example.com", password = "password", + duration = 60, // (optional) ) ``` diff --git a/docs/examples/kotlin/account/delete-sessions.md b/docs/examples/kotlin/account/delete-sessions.md index ff404dbc..96bbc341 100644 --- a/docs/examples/kotlin/account/delete-sessions.md +++ b/docs/examples/kotlin/account/delete-sessions.md @@ -9,5 +9,7 @@ val client = Client(context) val account = Account(client) -val result = account.deleteSessions() +val result = account.deleteSessions( + current = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/account/list-sessions.md b/docs/examples/kotlin/account/list-sessions.md index 8c261a2d..38e7e556 100644 --- a/docs/examples/kotlin/account/list-sessions.md +++ b/docs/examples/kotlin/account/list-sessions.md @@ -9,5 +9,7 @@ val client = Client(context) val account = Account(client) -val result = account.listSessions() +val result = account.listSessions( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/apps/create-installation-token.md b/docs/examples/kotlin/apps/create-installation-token.md new file mode 100644 index 00000000..6226e775 --- /dev/null +++ b/docs/examples/kotlin/apps/create-installation-token.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.createInstallationToken( + appId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/apps/create-key.md b/docs/examples/kotlin/apps/create-key.md new file mode 100644 index 00000000..d6daef84 --- /dev/null +++ b/docs/examples/kotlin/apps/create-key.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.createKey( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/create-secret.md b/docs/examples/kotlin/apps/create-secret.md new file mode 100644 index 00000000..d9a4c525 --- /dev/null +++ b/docs/examples/kotlin/apps/create-secret.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.createSecret( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/create.md b/docs/examples/kotlin/apps/create.md new file mode 100644 index 00000000..2299c031 --- /dev/null +++ b/docs/examples/kotlin/apps/create.md @@ -0,0 +1,33 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.create( + appId = "", + name = "", + redirectUris = listOf(), + description = "", // (optional) + clientUri = "https://example.com", // (optional) + logoUri = "https://example.com", // (optional) + privacyPolicyUrl = "https://example.com", // (optional) + termsUrl = "https://example.com", // (optional) + contacts = listOf(), // (optional) + tagline = "", // (optional) + tags = listOf(), // (optional) + images = listOf(), // (optional) + supportUrl = "https://example.com", // (optional) + dataDeletionUrl = "https://example.com", // (optional) + postLogoutRedirectUris = listOf(), // (optional) + enabled = false, // (optional) + type = "public", // (optional) + deviceFlow = false, // (optional) + teamId = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/delete-installation.md b/docs/examples/kotlin/apps/delete-installation.md new file mode 100644 index 00000000..c07fee53 --- /dev/null +++ b/docs/examples/kotlin/apps/delete-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteInstallation( + appId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete-key.md b/docs/examples/kotlin/apps/delete-key.md new file mode 100644 index 00000000..3ad3825a --- /dev/null +++ b/docs/examples/kotlin/apps/delete-key.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteKey( + appId = "", + keyId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete-secret.md b/docs/examples/kotlin/apps/delete-secret.md new file mode 100644 index 00000000..ad22d5e0 --- /dev/null +++ b/docs/examples/kotlin/apps/delete-secret.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteSecret( + appId = "", + secretId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete-tokens.md b/docs/examples/kotlin/apps/delete-tokens.md new file mode 100644 index 00000000..e8d89009 --- /dev/null +++ b/docs/examples/kotlin/apps/delete-tokens.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.deleteTokens( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/delete.md b/docs/examples/kotlin/apps/delete.md new file mode 100644 index 00000000..412b55f6 --- /dev/null +++ b/docs/examples/kotlin/apps/delete.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.delete( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get-installation.md b/docs/examples/kotlin/apps/get-installation.md new file mode 100644 index 00000000..288f21d6 --- /dev/null +++ b/docs/examples/kotlin/apps/get-installation.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.getInstallation( + appId = "", + installationId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get-key.md b/docs/examples/kotlin/apps/get-key.md new file mode 100644 index 00000000..8af73116 --- /dev/null +++ b/docs/examples/kotlin/apps/get-key.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.getKey( + appId = "", + keyId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get-secret.md b/docs/examples/kotlin/apps/get-secret.md new file mode 100644 index 00000000..a76bbe4e --- /dev/null +++ b/docs/examples/kotlin/apps/get-secret.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.getSecret( + appId = "", + secretId = "", +) +``` diff --git a/docs/examples/kotlin/apps/get.md b/docs/examples/kotlin/apps/get.md new file mode 100644 index 00000000..3fc30c71 --- /dev/null +++ b/docs/examples/kotlin/apps/get.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.get( + appId = "", +) +``` diff --git a/docs/examples/kotlin/apps/list-installation-scopes.md b/docs/examples/kotlin/apps/list-installation-scopes.md new file mode 100644 index 00000000..bafacf81 --- /dev/null +++ b/docs/examples/kotlin/apps/list-installation-scopes.md @@ -0,0 +1,13 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listInstallationScopes() +``` diff --git a/docs/examples/kotlin/apps/list-installations.md b/docs/examples/kotlin/apps/list-installations.md new file mode 100644 index 00000000..0819d5fc --- /dev/null +++ b/docs/examples/kotlin/apps/list-installations.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listInstallations( + appId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/list-keys.md b/docs/examples/kotlin/apps/list-keys.md new file mode 100644 index 00000000..9977dfe7 --- /dev/null +++ b/docs/examples/kotlin/apps/list-keys.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listKeys( + appId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/list-o-auth-2-scopes.md b/docs/examples/kotlin/apps/list-o-auth-2-scopes.md new file mode 100644 index 00000000..b0852955 --- /dev/null +++ b/docs/examples/kotlin/apps/list-o-auth-2-scopes.md @@ -0,0 +1,13 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listOAuth2Scopes() +``` diff --git a/docs/examples/kotlin/apps/list-secrets.md b/docs/examples/kotlin/apps/list-secrets.md new file mode 100644 index 00000000..aa69cc8c --- /dev/null +++ b/docs/examples/kotlin/apps/list-secrets.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.listSecrets( + appId = "", + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/list.md b/docs/examples/kotlin/apps/list.md new file mode 100644 index 00000000..d2faad48 --- /dev/null +++ b/docs/examples/kotlin/apps/list.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.list( + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/apps/update-team.md b/docs/examples/kotlin/apps/update-team.md new file mode 100644 index 00000000..b6f69443 --- /dev/null +++ b/docs/examples/kotlin/apps/update-team.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.updateTeam( + appId = "", + teamId = "", +) +``` diff --git a/docs/examples/kotlin/apps/update.md b/docs/examples/kotlin/apps/update.md new file mode 100644 index 00000000..b2bad705 --- /dev/null +++ b/docs/examples/kotlin/apps/update.md @@ -0,0 +1,34 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Apps + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val apps = Apps(client) + +val result = apps.update( + appId = "", + name = "", + description = "", // (optional) + clientUri = "https://example.com", // (optional) + logoUri = "https://example.com", // (optional) + privacyPolicyUrl = "https://example.com", // (optional) + termsUrl = "https://example.com", // (optional) + contacts = listOf(), // (optional) + tagline = "", // (optional) + tags = listOf(), // (optional) + images = listOf(), // (optional) + supportUrl = "https://example.com", // (optional) + dataDeletionUrl = "https://example.com", // (optional) + enabled = false, // (optional) + redirectUris = listOf(), // (optional) + postLogoutRedirectUris = listOf(), // (optional) + type = "public", // (optional) + deviceFlow = false, // (optional) + installationScopes = listOf(), // (optional) + installationRedirectUrl = "https://example.com", // (optional) +) +``` diff --git a/docs/examples/kotlin/avatars/delete-photo.md b/docs/examples/kotlin/avatars/delete-photo.md new file mode 100644 index 00000000..e16f4173 --- /dev/null +++ b/docs/examples/kotlin/avatars/delete-photo.md @@ -0,0 +1,13 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val avatars = Avatars(client) + +val result = avatars.deletePhoto() +``` diff --git a/docs/examples/kotlin/avatars/get-screenshot.md b/docs/examples/kotlin/avatars/get-screenshot.md index 1093c060..fcfb36c8 100644 --- a/docs/examples/kotlin/avatars/get-screenshot.md +++ b/docs/examples/kotlin/avatars/get-screenshot.md @@ -16,12 +16,11 @@ val avatars = Avatars(client) val result = avatars.getScreenshot( url = "https://example.com", headers = mapOf( - "Authorization" to "Bearer token123", - "X-Custom-Header" to "value" + "Accept-Language" to "en-US,en;q=0.9" ), // (optional) viewportWidth = 1920, // (optional) viewportHeight = 1080, // (optional) - scale = 2, // (optional) + scale = 2.0, // (optional) theme = BrowserTheme.LIGHT, // (optional) userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15", // (optional) fullpage = true, // (optional) @@ -29,7 +28,7 @@ val result = avatars.getScreenshot( timezone = Timezone.AFRICA_ABIDJAN, // (optional) latitude = 37.7749, // (optional) longitude = -122.4194, // (optional) - accuracy = 100, // (optional) + accuracy = 100.0, // (optional) touch = true, // (optional) permissions = BrowserPermission.GEOLOCATION, // (optional) sleep = 3, // (optional) diff --git a/docs/examples/kotlin/avatars/update-photo.md b/docs/examples/kotlin/avatars/update-photo.md new file mode 100644 index 00000000..326fe57a --- /dev/null +++ b/docs/examples/kotlin/avatars/update-photo.md @@ -0,0 +1,16 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.models.InputFile +import io.appwrite.services.Avatars + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val avatars = Avatars(client) + +val result = avatars.updatePhoto( + file = InputFile.fromPath("file.png"), +) +``` diff --git a/docs/examples/kotlin/databases/decrement-document-attribute.md b/docs/examples/kotlin/databases/decrement-document-attribute.md index c32a6002..07452b01 100644 --- a/docs/examples/kotlin/databases/decrement-document-attribute.md +++ b/docs/examples/kotlin/databases/decrement-document-attribute.md @@ -14,8 +14,8 @@ val result = databases.decrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - min = 0, // (optional) + value = 1.0, // (optional) + min = 0.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/databases/increment-document-attribute.md b/docs/examples/kotlin/databases/increment-document-attribute.md index daaa6d6a..e71fddc7 100644 --- a/docs/examples/kotlin/databases/increment-document-attribute.md +++ b/docs/examples/kotlin/databases/increment-document-attribute.md @@ -14,8 +14,8 @@ val result = databases.incrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - max = 100, // (optional) + value = 1.0, // (optional) + max = 100.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/databases/list-transactions.md b/docs/examples/kotlin/databases/list-transactions.md index 3cabd79f..e4cd4633 100644 --- a/docs/examples/kotlin/databases/list-transactions.md +++ b/docs/examples/kotlin/databases/list-transactions.md @@ -11,5 +11,6 @@ val databases = Databases(client) val result = databases.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/create-documents.md b/docs/examples/kotlin/documentsdb/create-documents.md index 2923c100..b66cb47d 100644 --- a/docs/examples/kotlin/documentsdb/create-documents.md +++ b/docs/examples/kotlin/documentsdb/create-documents.md @@ -12,7 +12,14 @@ val documentsDB = DocumentsDB(client) val result = documentsDB.createDocuments( databaseId = "", collectionId = "", - documents = listOf(), + documents = listOf(mapOf( + "\$id" to "example1", + "username" to "walter.obrien", + "email" to "walter.obrien@example.com", + "fullName" to "Walter O'Brien", + "age" to 30, + "isAdmin" to false + )), transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/decrement-document-attribute.md b/docs/examples/kotlin/documentsdb/decrement-document-attribute.md index fdb11fe0..461a2fe0 100644 --- a/docs/examples/kotlin/documentsdb/decrement-document-attribute.md +++ b/docs/examples/kotlin/documentsdb/decrement-document-attribute.md @@ -14,8 +14,8 @@ val result = documentsDB.decrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - min = 0, // (optional) + value = 1.0, // (optional) + min = 0.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/increment-document-attribute.md b/docs/examples/kotlin/documentsdb/increment-document-attribute.md index 8286ad44..0539e4a2 100644 --- a/docs/examples/kotlin/documentsdb/increment-document-attribute.md +++ b/docs/examples/kotlin/documentsdb/increment-document-attribute.md @@ -14,8 +14,8 @@ val result = documentsDB.incrementDocumentAttribute( collectionId = "", documentId = "", attribute = "", - value = 1, // (optional) - max = 100, // (optional) + value = 1.0, // (optional) + max = 100.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/documentsdb/list-transactions.md b/docs/examples/kotlin/documentsdb/list-transactions.md index 46571468..a1a47a2a 100644 --- a/docs/examples/kotlin/documentsdb/list-transactions.md +++ b/docs/examples/kotlin/documentsdb/list-transactions.md @@ -11,5 +11,6 @@ val documentsDB = DocumentsDB(client) val result = documentsDB.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/docs/examples/kotlin/graphql/mutation.md b/docs/examples/kotlin/graphql/mutation.md index 0cdb78e6..de686191 100644 --- a/docs/examples/kotlin/graphql/mutation.md +++ b/docs/examples/kotlin/graphql/mutation.md @@ -10,6 +10,8 @@ val client = Client(context) val graphql = Graphql(client) val result = graphql.mutation( - query = mapOf( "a" to "b" ), + query = mapOf( + "query" to "mutation { accountUpdateName(name: \"Walter\") { name } }" + ), ) ``` diff --git a/docs/examples/kotlin/graphql/query.md b/docs/examples/kotlin/graphql/query.md index 3359822a..cd0326da 100644 --- a/docs/examples/kotlin/graphql/query.md +++ b/docs/examples/kotlin/graphql/query.md @@ -10,6 +10,8 @@ val client = Client(context) val graphql = Graphql(client) val result = graphql.query( - query = mapOf( "a" to "b" ), + query = mapOf( + "query" to "query { localeGet { ip } }" + ), ) ``` diff --git a/docs/examples/kotlin/locale/list-codes.md b/docs/examples/kotlin/locale/list-codes.md index d7052144..31a89fc9 100644 --- a/docs/examples/kotlin/locale/list-codes.md +++ b/docs/examples/kotlin/locale/list-codes.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCodes() +val result = locale.listCodes( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-continents.md b/docs/examples/kotlin/locale/list-continents.md index 03e917e4..a5ce1c20 100644 --- a/docs/examples/kotlin/locale/list-continents.md +++ b/docs/examples/kotlin/locale/list-continents.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listContinents() +val result = locale.listContinents( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-countries-eu.md b/docs/examples/kotlin/locale/list-countries-eu.md index a933ed85..9214428e 100644 --- a/docs/examples/kotlin/locale/list-countries-eu.md +++ b/docs/examples/kotlin/locale/list-countries-eu.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCountriesEU() +val result = locale.listCountriesEU( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-countries-phones.md b/docs/examples/kotlin/locale/list-countries-phones.md index 8d4c7534..50355d79 100644 --- a/docs/examples/kotlin/locale/list-countries-phones.md +++ b/docs/examples/kotlin/locale/list-countries-phones.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCountriesPhones() +val result = locale.listCountriesPhones( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-countries.md b/docs/examples/kotlin/locale/list-countries.md index e67b5fe7..2a762a53 100644 --- a/docs/examples/kotlin/locale/list-countries.md +++ b/docs/examples/kotlin/locale/list-countries.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCountries() +val result = locale.listCountries( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-currencies.md b/docs/examples/kotlin/locale/list-currencies.md index fb0087d7..a25d489b 100644 --- a/docs/examples/kotlin/locale/list-currencies.md +++ b/docs/examples/kotlin/locale/list-currencies.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listCurrencies() +val result = locale.listCurrencies( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/locale/list-languages.md b/docs/examples/kotlin/locale/list-languages.md index 30fb65bf..acaac2e9 100644 --- a/docs/examples/kotlin/locale/list-languages.md +++ b/docs/examples/kotlin/locale/list-languages.md @@ -9,5 +9,7 @@ val client = Client(context) val locale = Locale(client) -val result = locale.listLanguages() +val result = locale.listLanguages( + total = false, // (optional) +) ``` diff --git a/docs/examples/kotlin/oauth2/approve.md b/docs/examples/kotlin/oauth2/approve.md new file mode 100644 index 00000000..839a862d --- /dev/null +++ b/docs/examples/kotlin/oauth2/approve.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.approve( + grantId = "", + authorizationDetails = "", // (optional) + scope = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/authorize-post.md b/docs/examples/kotlin/oauth2/authorize-post.md new file mode 100644 index 00000000..88bc4ee0 --- /dev/null +++ b/docs/examples/kotlin/oauth2/authorize-post.md @@ -0,0 +1,28 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.authorizePost( + clientId = "", // (optional) + redirectUri = "https://example.com", // (optional) + responseType = "", // (optional) + scope = "", // (optional) + state = "", // (optional) + nonce = "", // (optional) + codeChallenge = "", // (optional) + codeChallengeMethod = "s256", // (optional) + prompt = "", // (optional) + maxAge = 0, // (optional) + authorizationDetails = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) + requestUri = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/authorize.md b/docs/examples/kotlin/oauth2/authorize.md new file mode 100644 index 00000000..4d307e8d --- /dev/null +++ b/docs/examples/kotlin/oauth2/authorize.md @@ -0,0 +1,28 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.authorize( + clientId = "", // (optional) + redirectUri = "https://example.com", // (optional) + responseType = "", // (optional) + scope = "", // (optional) + state = "", // (optional) + nonce = "", // (optional) + codeChallenge = "", // (optional) + codeChallengeMethod = "s256", // (optional) + prompt = "", // (optional) + maxAge = 0, // (optional) + authorizationDetails = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) + requestUri = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/create-device-authorization.md b/docs/examples/kotlin/oauth2/create-device-authorization.md new file mode 100644 index 00000000..ab307c37 --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-device-authorization.md @@ -0,0 +1,19 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createDeviceAuthorization( + clientId = "", // (optional) + scope = "", // (optional) + authorizationDetails = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/create-grant.md b/docs/examples/kotlin/oauth2/create-grant.md new file mode 100644 index 00000000..74f57c03 --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-grant.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createGrant( + userCode = "", +) +``` diff --git a/docs/examples/kotlin/oauth2/create-par.md b/docs/examples/kotlin/oauth2/create-par.md new file mode 100644 index 00000000..752c64eb --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-par.md @@ -0,0 +1,27 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createPAR( + clientId = "", + redirectUri = "https://example.com", + responseType = "code", + scope = "", // (optional) + state = "", // (optional) + nonce = "", // (optional) + codeChallenge = "", // (optional) + codeChallengeMethod = "s256", // (optional) + prompt = "", // (optional) + maxAge = 0, // (optional) + authorizationDetails = "", // (optional) + resource = "", // (optional) + audience = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/create-token.md b/docs/examples/kotlin/oauth2/create-token.md new file mode 100644 index 00000000..5e23344f --- /dev/null +++ b/docs/examples/kotlin/oauth2/create-token.md @@ -0,0 +1,24 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.createToken( + grantType = "", + code = "", // (optional) + refreshToken = "", // (optional) + deviceCode = "", // (optional) + clientId = "", // (optional) + clientSecret = "", // (optional) + codeVerifier = "", // (optional) + redirectUri = "https://example.com", // (optional) + resource = "", // (optional) + audience = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/get-grant.md b/docs/examples/kotlin/oauth2/get-grant.md new file mode 100644 index 00000000..4e5516be --- /dev/null +++ b/docs/examples/kotlin/oauth2/get-grant.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.getGrant( + grantId = "", +) +``` diff --git a/docs/examples/kotlin/oauth2/list-organizations.md b/docs/examples/kotlin/oauth2/list-organizations.md new file mode 100644 index 00000000..463ebec0 --- /dev/null +++ b/docs/examples/kotlin/oauth2/list-organizations.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.listOrganizations( + limit = 1, // (optional) + offset = 0, // (optional) + search = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/list-projects.md b/docs/examples/kotlin/oauth2/list-projects.md new file mode 100644 index 00000000..2105828f --- /dev/null +++ b/docs/examples/kotlin/oauth2/list-projects.md @@ -0,0 +1,17 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.listProjects( + limit = 1, // (optional) + offset = 0, // (optional) + search = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/logout-post.md b/docs/examples/kotlin/oauth2/logout-post.md new file mode 100644 index 00000000..48bd14ee --- /dev/null +++ b/docs/examples/kotlin/oauth2/logout-post.md @@ -0,0 +1,20 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.logoutPost( + idTokenHint = "", // (optional) + logoutHint = "", // (optional) + clientId = "", // (optional) + postLogoutRedirectUri = "https://example.com", // (optional) + state = "", // (optional) + uiLocales = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/logout.md b/docs/examples/kotlin/oauth2/logout.md new file mode 100644 index 00000000..08f2c1c1 --- /dev/null +++ b/docs/examples/kotlin/oauth2/logout.md @@ -0,0 +1,20 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.logout( + idTokenHint = "", // (optional) + logoutHint = "", // (optional) + clientId = "", // (optional) + postLogoutRedirectUri = "https://example.com", // (optional) + state = "", // (optional) + uiLocales = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/reject.md b/docs/examples/kotlin/oauth2/reject.md new file mode 100644 index 00000000..f4392496 --- /dev/null +++ b/docs/examples/kotlin/oauth2/reject.md @@ -0,0 +1,15 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.reject( + grantId = "", +) +``` diff --git a/docs/examples/kotlin/oauth2/revoke.md b/docs/examples/kotlin/oauth2/revoke.md new file mode 100644 index 00000000..f5ef08b7 --- /dev/null +++ b/docs/examples/kotlin/oauth2/revoke.md @@ -0,0 +1,18 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Oauth2 + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val oauth2 = Oauth2(client) + +val result = oauth2.revoke( + token = "", + tokenTypeHint = "access_token", // (optional) + clientId = "", // (optional) + clientSecret = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/storage/get-file-preview.md b/docs/examples/kotlin/storage/get-file-preview.md index e2d2851d..bd106928 100644 --- a/docs/examples/kotlin/storage/get-file-preview.md +++ b/docs/examples/kotlin/storage/get-file-preview.md @@ -21,7 +21,7 @@ val result = storage.getFilePreview( borderWidth = 0, // (optional) borderColor = "FFFFFF", // (optional) borderRadius = 0, // (optional) - opacity = 0, // (optional) + opacity = 0.0, // (optional) rotation = -360, // (optional) background = "FFFFFF", // (optional) output = ImageFormat.JPG, // (optional) diff --git a/docs/examples/kotlin/tablesdb/decrement-row-column.md b/docs/examples/kotlin/tablesdb/decrement-row-column.md index 157c3d11..b8288ad3 100644 --- a/docs/examples/kotlin/tablesdb/decrement-row-column.md +++ b/docs/examples/kotlin/tablesdb/decrement-row-column.md @@ -14,8 +14,8 @@ val result = tablesDB.decrementRowColumn( tableId = "", rowId = "", column = "", - value = 1, // (optional) - min = 0, // (optional) + value = 1.0, // (optional) + min = 0.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/tablesdb/increment-row-column.md b/docs/examples/kotlin/tablesdb/increment-row-column.md index 802c6a2b..1aae1ffc 100644 --- a/docs/examples/kotlin/tablesdb/increment-row-column.md +++ b/docs/examples/kotlin/tablesdb/increment-row-column.md @@ -14,8 +14,8 @@ val result = tablesDB.incrementRowColumn( tableId = "", rowId = "", column = "", - value = 1, // (optional) - max = 100, // (optional) + value = 1.0, // (optional) + max = 100.0, // (optional) transactionId = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/tablesdb/list-transactions.md b/docs/examples/kotlin/tablesdb/list-transactions.md index 03c9d5b4..b680ee15 100644 --- a/docs/examples/kotlin/tablesdb/list-transactions.md +++ b/docs/examples/kotlin/tablesdb/list-transactions.md @@ -11,5 +11,6 @@ val tablesDB = TablesDB(client) val result = tablesDB.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/docs/examples/kotlin/teams/create-membership.md b/docs/examples/kotlin/teams/create-membership.md index e5289e64..219119af 100644 --- a/docs/examples/kotlin/teams/create-membership.md +++ b/docs/examples/kotlin/teams/create-membership.md @@ -11,7 +11,7 @@ val teams = Teams(client) val result = teams.createMembership( teamId = "", - roles = listOf(), + roles = listOf("editor"), email = "email@example.com", // (optional) userId = "", // (optional) phone = "+12065550100", // (optional) diff --git a/docs/examples/kotlin/teams/update-membership.md b/docs/examples/kotlin/teams/update-membership.md index 6e721d4e..d9e39d7b 100644 --- a/docs/examples/kotlin/teams/update-membership.md +++ b/docs/examples/kotlin/teams/update-membership.md @@ -12,6 +12,6 @@ val teams = Teams(client) val result = teams.updateMembership( teamId = "", membershipId = "", - roles = listOf(), + roles = listOf("editor"), ) ``` diff --git a/docs/examples/kotlin/vectorsdb/create-operations.md b/docs/examples/kotlin/vectorsdb/create-operations.md index 1a2908af..8bd201a3 100644 --- a/docs/examples/kotlin/vectorsdb/create-operations.md +++ b/docs/examples/kotlin/vectorsdb/create-operations.md @@ -17,7 +17,10 @@ val result = vectorsDB.createOperations( "collectionId" to "", "documentId" to "", "data" to mapOf( - "name" to "Walter O'Brien" + "embeddings" to listOf(0.12, -0.55, 0.88, 1.02), + "metadata" to mapOf( + "name" to "First document" + ) ) )), // (optional) ) diff --git a/docs/examples/kotlin/vectorsdb/list-transactions.md b/docs/examples/kotlin/vectorsdb/list-transactions.md index 97981c01..76fb10e3 100644 --- a/docs/examples/kotlin/vectorsdb/list-transactions.md +++ b/docs/examples/kotlin/vectorsdb/list-transactions.md @@ -11,5 +11,6 @@ val vectorsDB = VectorsDB(client) val result = vectorsDB.listTransactions( queries = listOf(), // (optional) + total = false, // (optional) ) ``` diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 45641217..3a60a6b3 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -46,9 +46,11 @@ android { } defaultConfig { - minSdk = 23 + minSdk = 24 buildConfigField("String", "SDK_VERSION", "\"$publishVersion\"") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + // Push: R8 rules for the bundled MQTT client, applied to consuming apps. + consumerProguardFiles("consumer-rules.pro") } buildTypes { @@ -82,6 +84,7 @@ dependencies { api("com.squareup.okhttp3:okhttp:5.5.0") implementation("com.google.code.gson:gson:2.14.0") + implementation("com.hivemq:hivemq-mqtt-client-shaded:1.3.6") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0") implementation("androidx.lifecycle:lifecycle-common-java8:2.11.0") diff --git a/library/consumer-rules.pro b/library/consumer-rules.pro new file mode 100644 index 00000000..04d7db2c --- /dev/null +++ b/library/consumer-rules.pro @@ -0,0 +1,27 @@ +# Push: rules applied to apps that shrink with R8. +# +# The shaded HiveMQ MQTT client relocates Netty and JCTools under +# com.hivemq.client.internal.shaded. Netty references optional classes that are never loaded +# on Android: native transports, the HTTP/WebSocket codecs, proxy handlers, alternative TLS +# providers (tcnative, BouncyCastle, Conscrypt, Jetty ALPN) and logging backends. +-dontwarn com.hivemq.client.internal.shaded.io.netty.channel.epoll.** +-dontwarn com.hivemq.client.internal.shaded.io.netty.channel.kqueue.** +-dontwarn com.hivemq.client.internal.shaded.io.netty.handler.codec.http.** +-dontwarn com.hivemq.client.internal.shaded.io.netty.handler.proxy.** +-dontwarn com.hivemq.client.internal.shaded.io.netty.internal.tcnative.** +-dontwarn org.bouncycastle.** +-dontwarn org.conscrypt.** +-dontwarn org.eclipse.jetty.** +-dontwarn org.apache.log4j.** +-dontwarn org.apache.logging.log4j.** +-dontwarn org.slf4j.** +-dontwarn reactor.blockhound.** + +# Netty and JCTools reach their own fields by name, through atomic field updaters and Unsafe offsets. +-keepclassmembernames class com.hivemq.client.internal.shaded.io.netty.** { ; } +-keepclassmembers class com.hivemq.client.internal.shaded.org.jctools.** { ; } + +# Netty's leak detector looks these methods up by name when its classes load. +-keepclassmembers class com.hivemq.client.internal.shaded.io.netty.buffer.AbstractByteBufAllocator { *** toLeakAwareBuffer(...); } +-keepclassmembers class com.hivemq.client.internal.shaded.io.netty.buffer.AdvancedLeakAwareByteBuf { *** recordLeakNonRefCountingOperation(...); } +-keepclassmembers class com.hivemq.client.internal.shaded.io.netty.util.ReferenceCountUtil { *** touch(...); } diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 899321de..44f894ed 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -3,5 +3,39 @@ + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 40431217..c364e835 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -8,6 +8,7 @@ import io.appwrite.cookies.stores.SharedPreferencesCookieStore import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.fromJson import io.appwrite.extensions.toJsonRequestBody +import io.appwrite.extensions.toJsonWithNulls import io.appwrite.models.InputFile import io.appwrite.models.UploadProgress import kotlinx.coroutines.CoroutineScope @@ -49,7 +50,11 @@ class Client @JvmOverloads constructor( var endpoint: String = "https://cloud.appwrite.io/v1", @set:JvmSynthetic var endpointRealtime: String? = null, - private var selfSigned: Boolean = false + private var selfSigned: Boolean = false, + @set:JvmSynthetic + var endpointPush: String? = null, + @set:JvmSynthetic + var pushClientId: String? = null ) : CoroutineScope { companion object { @@ -96,7 +101,7 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "28.0.0", + "x-sdk-version" to "29.0.0", "x-appwrite-response-format" to "2.3.0" ) config = mutableMapOf() @@ -324,6 +329,37 @@ class Client @JvmOverloads constructor( return this } + /** + * Set push endpoint + * + * The MQTT broker URL the [io.appwrite.services.Push] service connects to, e.g. + * `mqtt://host:1883` or `mqtts://host:8883`. + * + * @param endpoint + * + * @return this + */ + fun setPushEndpoint(endpoint: String): Client { + this.endpointPush = endpoint + return this + } + + /** + * Set push client id + * + * A stable client id for the [io.appwrite.services.Push] service. The broker keys its + * offline-replay cursor on this id, so pass a stable value to resume replay across + * restarts. Defaults to the userId decoded from the JWT (else the credential) when unset. + * + * @param pushClientId + * + * @return this + */ + fun setPushClientId(pushClientId: String): Client { + this.pushClientId = pushClientId + return this + } + /** * Add Header * @@ -439,9 +475,12 @@ class Client @JvmOverloads constructor( filteredParams.forEach { when { - it.key == "file" -> { + it.value is MultipartBody.Part -> { builder.addPart(it.value as MultipartBody.Part) } + it.value is Map<*, *> -> { + builder.addFormDataPart(it.key, (it.value as Map<*, *>).toJsonWithNulls()) + } it.value is List<*> -> { val list = it.value as List<*> for (index in list.indices) { @@ -508,11 +547,21 @@ class Client @JvmOverloads constructor( headers: MutableMap, params: MutableMap, responseType: Class, - converter: ((Any) -> T), + converter: ((Any) -> T)? = null, paramName: String, idParamName: String? = null, onProgress: ((UploadProgress) -> Unit)? = null, ): T { + if (params[paramName] == null) { + return call( + method = "POST", + path, + headers, + params, + responseType, + converter + ) + } val input = params[paramName] as InputFile val size: Long = when (input.sourceType) { "path", "file" -> { @@ -524,7 +573,7 @@ class Client @JvmOverloads constructor( else -> throw UnsupportedOperationException() } - if (size < CHUNK_SIZE) { + if (size < CHUNK_SIZE || responseType == String::class.java) { val data = when (input.sourceType) { "file", "path" -> File(input.path).asRequestBody() "bytes" -> (input.data as ByteArray).toRequestBody(input.mimeType.toMediaType()) @@ -690,7 +739,7 @@ class Client @JvmOverloads constructor( result = completedResultRef.get() ?: lastResultRef.get() } - return converter(result as Map) + return converter?.invoke(result as Map) ?: result as T } /** diff --git a/library/src/main/java/io/appwrite/ID.kt b/library/src/main/java/io/appwrite/ID.kt index cf415258..93f948fc 100644 --- a/library/src/main/java/io/appwrite/ID.kt +++ b/library/src/main/java/io/appwrite/ID.kt @@ -1,6 +1,5 @@ package io.appwrite -import java.time.Instant import kotlin.math.floor import kotlin.random.Random @@ -12,8 +11,7 @@ class ID { // Generate an hex ID based on timestamp // Recreated from https://www.php.net/manual/en/function.uniqid.php private fun hexTimestamp(): String { - val now = Instant.now() - val sec = now.epochSecond + val sec = System.currentTimeMillis() / 1000 val usec = (System.nanoTime() / 1000) % 1000 val hexTimestamp = "%08x%05x".format(sec, usec) diff --git a/library/src/main/java/io/appwrite/Operator.kt b/library/src/main/java/io/appwrite/Operator.kt index 578dc087..74ab2cb9 100644 --- a/library/src/main/java/io/appwrite/Operator.kt +++ b/library/src/main/java/io/appwrite/Operator.kt @@ -22,6 +22,13 @@ class Operator( ) { override fun toString() = this.toJson() + fun toMap(): Map = mapOf( + "method" to method, + "values" to values, + ).filterValues { it != null } + + fun toJson(): String = toMap().toJson() + companion object { fun increment(value: Number = 1, max: Number? = null): String { require(!value.toDouble().isNaN() && !value.toDouble().isInfinite()) { "Value cannot be NaN or Infinity" } diff --git a/library/src/main/java/io/appwrite/Query.kt b/library/src/main/java/io/appwrite/Query.kt index b0d4ab0f..23f100df 100644 --- a/library/src/main/java/io/appwrite/Query.kt +++ b/library/src/main/java/io/appwrite/Query.kt @@ -18,6 +18,14 @@ class Query( */ override fun toString() = this.toJson() + fun toMap(): Map = mapOf( + "method" to method, + "attribute" to attribute, + "values" to values, + ).filterValues { it != null } + + fun toJson(): String = toMap().toJson() + companion object { /** @@ -350,7 +358,7 @@ class Query( * @param queries The list of query strings to combine. * @returns The query string. */ - fun or(queries: List) = Query("or", null, queries.map { it.fromJson() }).toJson() + fun or(queries: List) = Query("or", null, queries.map { it.fromJson>() }).toJson() /** * Combine multiple queries using logical AND operator. @@ -358,7 +366,7 @@ class Query( * @param queries The list of query strings to combine. * @returns The query string. */ - fun and(queries: List) = Query("and", null, queries.map { it.fromJson() }).toJson() + fun and(queries: List) = Query("and", null, queries.map { it.fromJson>() }).toJson() /** * Filter array elements where at least one element matches all the specified queries. @@ -367,7 +375,7 @@ class Query( * @param queries The list of query strings to match against array elements. * @returns The query string. */ - fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson() }).toJson() + fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson>() }).toJson() /** * Filter resources where attribute is at a specific distance from the given coordinates. diff --git a/library/src/main/java/io/appwrite/Topic.kt b/library/src/main/java/io/appwrite/Topic.kt new file mode 100644 index 00000000..8945da0e --- /dev/null +++ b/library/src/main/java/io/appwrite/Topic.kt @@ -0,0 +1,68 @@ +package io.appwrite + +/** + * Validate the levels passed to `path()`: a non-empty list of non-empty strings, none holding + * the level separator `/` or the wildcards `+` and `#` (use `any()` and `all()` for those). + */ +private fun levelsOf(levels: List): List { + require(levels.isNotEmpty()) { "path() needs at least one level" } + levels.forEachIndexed { index, level -> + require(level.isNotEmpty()) { "empty level at index $index" } + require(!level.contains("/")) { + val parts = level.split("/").joinToString(", ") { "'$it'" } + "\"$level\" contains \"/\", split it: path([$parts])" + } + require(level != "+") { "\"+\" is reserved, use any()" } + require(!level.contains("+")) { "\"$level\" contains \"+\", use any() for wildcards" } + require(level != "#") { "\"#\" is reserved, use all()" } + require(!level.contains("#")) { "\"$level\" contains \"#\", use all() for wildcards" } + } + return levels.toList() +} + +/** + * Helper to build MQTT topics for [services.Push], level by level. + * + * ``` + * Topic.path(listOf("user", userId, "notification")) // "user/123/notification" + * Topic.path(listOf("user")).any().path(listOf("notification")) // "user/+/notification" + * Topic.path(listOf("org")).any().path(listOf("logs")).all() // "org/+/logs/#" + * ``` + * + * `any()` matches exactly one level and `all()` everything below, at any depth (including the + * parent itself). `all()` ends the topic, so it returns a [ResolvedTopic]. + * + * @throws IllegalArgumentException from `path()` when a level is empty or holds `/`, `+` or `#`. + */ +class Topic private constructor( + private val levels: List, +) { + /** Append the given levels. */ + fun path(levels: List): Topic = Topic(this.levels + levelsOf(levels)) + + /** Append a single-level wildcard (`+`): matches exactly one level. */ + fun any(): Topic = Topic(levels + "+") + + /** Append the multi-level wildcard (`#`) and end the topic. */ + fun all(): ResolvedTopic = ResolvedTopic(levels + "#") + + override fun toString(): String = levels.joinToString("/") + + companion object { + /** Start a topic with the given levels, e.g. `Topic.path(listOf("user", userId))`. */ + fun path(levels: List): Topic = Topic(levelsOf(levels)) + + /** Start a topic with a single-level wildcard (`+`). */ + fun any(): Topic = Topic(listOf("+")) + + /** The multi-level wildcard alone (`#`): matches every topic. */ + fun all(): ResolvedTopic = ResolvedTopic(listOf("#")) + } +} + +/** A topic ended by `all()`: nothing can be appended after the `#` wildcard. */ +class ResolvedTopic internal constructor( + private val levels: List, +) { + override fun toString(): String = levels.joinToString("/") +} diff --git a/library/src/main/java/io/appwrite/cookies/InternalCookie.kt b/library/src/main/java/io/appwrite/cookies/InternalCookie.kt index dfb8948a..6a1422b1 100644 --- a/library/src/main/java/io/appwrite/cookies/InternalCookie.kt +++ b/library/src/main/java/io/appwrite/cookies/InternalCookie.kt @@ -31,6 +31,38 @@ data class InternalCookie( cookie.version ) + companion object { + fun from(map: Map<*, *>) = InternalCookie( + comment = map["comment"] as? String, + commentURL = map["commentURL"] as? String, + discard = map["discard"] as? Boolean, + domain = map["domain"] as String, + maxAge = (map["maxAge"] as? Number)?.toLong(), + name = map["name"] as String, + path = map["path"] as? String, + portlist = map["portlist"] as? String, + secure = map["secure"] as? Boolean, + value = map["value"] as String, + version = (map["version"] as? Number)?.toInt(), + httpOnly = map["httpOnly"] as? Boolean, + ) + } + + fun toMap(): Map = mapOf( + "comment" to comment, + "commentURL" to commentURL, + "discard" to discard, + "domain" to domain, + "maxAge" to maxAge, + "name" to name, + "path" to path, + "portlist" to portlist, + "secure" to secure, + "value" to value, + "version" to version, + "httpOnly" to httpOnly, + ) + fun toHttpCookie() = HttpCookie(name, value).apply { comment = this@InternalCookie.comment commentURL = this@InternalCookie.commentURL diff --git a/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt b/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt index 61986f6c..85d97e0f 100644 --- a/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt +++ b/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt @@ -4,7 +4,6 @@ import android.content.SharedPreferences import android.os.Build import android.util.Log import com.google.gson.Gson -import com.google.gson.reflect.TypeToken import io.appwrite.cookies.InternalCookie import java.net.HttpCookie import java.net.URI @@ -20,9 +19,8 @@ open class SharedPreferencesCookieStore( preferences.all.forEach { (key, value) -> try { val index = URI.create(key) - val listType = object : TypeToken>() {}.type - val internalCookies = - gson.fromJson>(value.toString(), listType) + val internalCookies = gson.fromJson(value.toString(), List::class.java) + .map { InternalCookie.from(it as Map<*, *>) } val cookies = internalCookies.map { it.toHttpCookie() }.toMutableList() uriIndex[index] = cookies } catch (exception: Throwable) { @@ -59,8 +57,7 @@ open class SharedPreferencesCookieStore( } } - val listType = object : TypeToken>() {}.type - val json = gson.toJson(internalCookies, listType) + val json = gson.toJson(internalCookies.map { it.toMap() }) preferences .edit() @@ -82,8 +79,7 @@ open class SharedPreferencesCookieStore( } } } - val listType = object : TypeToken>() {}.type - val json = gson.toJson(internalCookies, listType) + val json = gson.toJson(internalCookies?.map { it.toMap() }) preferences.edit().apply { when (cookies) { diff --git a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt index 4eb5cbfd..265e7807 100644 --- a/library/src/main/java/io/appwrite/enums/OAuthProvider.kt +++ b/library/src/main/java/io/appwrite/enums/OAuthProvider.kt @@ -87,6 +87,8 @@ enum class OAuthProvider(val value: String) { TRADESHIFTBOX("tradeshiftBox"), @SerializedName("twitch") TWITCH("twitch"), + @SerializedName("webflow") + WEBFLOW("webflow"), @SerializedName("wordpress") WORDPRESS("wordpress"), @SerializedName("x") diff --git a/library/src/main/java/io/appwrite/models/Account.kt b/library/src/main/java/io/appwrite/models/Account.kt new file mode 100644 index 00000000..38865ff8 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Account.kt @@ -0,0 +1,266 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Account + */ +data class Account( + /** + * User ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * User creation date in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * User update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * User name. + */ + @SerializedName("name") + val name: String, + + /** + * User registration date in ISO 8601 format. + */ + @SerializedName("registration") + val registration: String, + + /** + * User status. Pass `true` for enabled and `false` for disabled. + */ + @SerializedName("status") + val status: Boolean, + + /** + * Labels for the user. + */ + @SerializedName("labels") + val labels: List, + + /** + * Password update time in ISO 8601 format. + */ + @SerializedName("passwordUpdate") + val passwordUpdate: String, + + /** + * User email address. + */ + @SerializedName("email") + val email: String, + + /** + * User phone number in E.164 format. + */ + @SerializedName("phone") + val phone: String, + + /** + * Email verification status. + */ + @SerializedName("emailVerification") + val emailVerification: Boolean, + + /** + * Canonical form of the user email address. + */ + @SerializedName("emailCanonical") + var emailCanonical: String?, + + /** + * Whether the user email is from a free email provider. + */ + @SerializedName("emailIsFree") + var emailIsFree: Boolean?, + + /** + * Whether the user email is from a disposable email provider. + */ + @SerializedName("emailIsDisposable") + var emailIsDisposable: Boolean?, + + /** + * Whether the user email is from a corporate domain. + */ + @SerializedName("emailIsCorporate") + var emailIsCorporate: Boolean?, + + /** + * Whether the user email is in its canonical form. + */ + @SerializedName("emailIsCanonical") + var emailIsCanonical: Boolean?, + + /** + * Whether the password was found in a known data breach the last time it was checked. Null when the password has never been checked. + */ + @SerializedName("passwordPwned") + var passwordPwned: Boolean?, + + /** + * Phone verification status. + */ + @SerializedName("phoneVerification") + val phoneVerification: Boolean, + + /** + * Multi factor authentication status. + */ + @SerializedName("mfa") + val mfa: Boolean, + + /** + * User preferences as a key-value object + */ + @SerializedName("prefs") + val prefs: Preferences, + + /** + * A user-owned message receiver. A single user may have multiple e.g. emails, phones, and a browser. Each target is registered with a single provider. + */ + @SerializedName("targets") + val targets: List, + + /** + * Most recent access date in ISO 8601 format. This attribute is only updated again after 24 hours. + */ + @SerializedName("accessedAt") + val accessedAt: String, + + /** + * Whether the user can impersonate other users. + */ + @SerializedName("impersonator") + var impersonator: Boolean?, + + /** + * ID of the original actor performing the impersonation. Present only when the current request is impersonating another user. Internal audit logs attribute the action to this user, while the impersonated target is recorded only in internal audit payload data. + */ + @SerializedName("impersonatorUserId") + var impersonatorUserId: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "name" to name as Any, + "registration" to registration as Any, + "status" to status as Any, + "labels" to labels as Any, + "passwordUpdate" to passwordUpdate as Any, + "email" to email as Any, + "phone" to phone as Any, + "emailVerification" to emailVerification as Any, + "emailCanonical" to emailCanonical as Any?, + "emailIsFree" to emailIsFree as Any?, + "emailIsDisposable" to emailIsDisposable as Any?, + "emailIsCorporate" to emailIsCorporate as Any?, + "emailIsCanonical" to emailIsCanonical as Any?, + "passwordPwned" to passwordPwned as Any?, + "phoneVerification" to phoneVerification as Any, + "mfa" to mfa as Any, + "prefs" to prefs.toMap() as Any, + "targets" to targets.map { it.toMap() } as Any, + "accessedAt" to accessedAt as Any, + "impersonator" to impersonator as Any?, + "impersonatorUserId" to impersonatorUserId as Any?, + ) + + companion object { + operator fun invoke( + id: String, + createdAt: String, + updatedAt: String, + name: String, + registration: String, + status: Boolean, + labels: List, + passwordUpdate: String, + email: String, + phone: String, + emailVerification: Boolean, + emailCanonical: String?, + emailIsFree: Boolean?, + emailIsDisposable: Boolean?, + emailIsCorporate: Boolean?, + emailIsCanonical: Boolean?, + passwordPwned: Boolean?, + phoneVerification: Boolean, + mfa: Boolean, + prefs: Preferences>, + targets: List, + accessedAt: String, + impersonator: Boolean?, + impersonatorUserId: String?, + ) = Account>( + id, + createdAt, + updatedAt, + name, + registration, + status, + labels, + passwordUpdate, + email, + phone, + emailVerification, + emailCanonical, + emailIsFree, + emailIsDisposable, + emailIsCorporate, + emailIsCanonical, + passwordPwned, + phoneVerification, + mfa, + prefs, + targets, + accessedAt, + impersonator, + impersonatorUserId, + ) + + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + nestedType: Class + ) = Account( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + name = map["name"] as String, + registration = map["registration"] as String, + status = map["status"] as Boolean, + labels = map["labels"] as List, + passwordUpdate = map["passwordUpdate"] as String, + email = map["email"] as String, + phone = map["phone"] as String, + emailVerification = map["emailVerification"] as Boolean, + emailCanonical = map["emailCanonical"] as? String, + emailIsFree = map["emailIsFree"] as? Boolean, + emailIsDisposable = map["emailIsDisposable"] as? Boolean, + emailIsCorporate = map["emailIsCorporate"] as? Boolean, + emailIsCanonical = map["emailIsCanonical"] as? Boolean, + passwordPwned = map["passwordPwned"] as? Boolean, + phoneVerification = map["phoneVerification"] as Boolean, + mfa = map["mfa"] as Boolean, + prefs = Preferences.from(map = map["prefs"] as Map, nestedType), + targets = (map["targets"] as List>).map { Target.from(map = it) }, + accessedAt = map["accessedAt"] as String, + impersonator = map["impersonator"] as? Boolean, + impersonatorUserId = map["impersonatorUserId"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/App.kt b/library/src/main/java/io/appwrite/models/App.kt new file mode 100644 index 00000000..f4a715c8 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/App.kt @@ -0,0 +1,229 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App + */ +data class App( + /** + * App ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * App creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * App update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application name. + */ + @SerializedName("name") + val name: String, + + /** + * Application description shown to users during OAuth2 consent. + */ + @SerializedName("description") + val description: String, + + /** + * Application homepage URL shown to users during OAuth2 consent. + */ + @SerializedName("clientUri") + val clientUri: String, + + /** + * Application logo URL shown to users during OAuth2 consent. + */ + @SerializedName("logoUri") + val logoUri: String, + + /** + * Application privacy policy URL shown to users during OAuth2 consent. + */ + @SerializedName("privacyPolicyUrl") + val privacyPolicyUrl: String, + + /** + * Application terms of service URL shown to users during OAuth2 consent. + */ + @SerializedName("termsUrl") + val termsUrl: String, + + /** + * Application support or security contact emails. + */ + @SerializedName("contacts") + val contacts: List, + + /** + * Application tagline shown to users during OAuth2 consent. + */ + @SerializedName("tagline") + val tagline: String, + + /** + * Application tags shown to users during OAuth2 consent. + */ + @SerializedName("tags") + val tags: List, + + /** + * Application labels. Read-only for clients; only a server SDK using a project API key can update them. + */ + @SerializedName("labels") + val labels: List, + + /** + * Application image URLs shown to users during OAuth2 consent. + */ + @SerializedName("images") + val images: List, + + /** + * Application support URL shown to users during OAuth2 consent. + */ + @SerializedName("supportUrl") + val supportUrl: String, + + /** + * Application data deletion URL shown to users during OAuth2 consent. + */ + @SerializedName("dataDeletionUrl") + val dataDeletionUrl: String, + + /** + * List of authorized redirect URIs. These URIs can be used to redirect users after they authenticate. + */ + @SerializedName("redirectUris") + val redirectUris: List, + + /** + * List of authorized post-logout redirect URIs for OpenID Connect RP-Initiated Logout. The logout endpoint only redirects users to URIs in this list after ending their session. + */ + @SerializedName("postLogoutRedirectUris") + val postLogoutRedirectUris: List, + + /** + * Whether the app is enabled or not. + */ + @SerializedName("enabled") + val enabled: Boolean, + + /** + * OAuth2 client type. `public` for SPAs, mobile, and native apps that cannot keep a client secret (PKCE required); `confidential` for server-side clients that authenticate with a client secret. + */ + @SerializedName("type") + val type: String, + + /** + * Whether this client may use the OAuth2 Device Authorization Grant (RFC 8628). + */ + @SerializedName("deviceFlow") + val deviceFlow: Boolean, + + /** + * ID of team that owns the application, if owned by team. Otherwise, user ID will be used. + */ + @SerializedName("teamId") + val teamId: String, + + /** + * ID of user who owns the application, if owned by user. Otherwise, team ID will be used. + */ + @SerializedName("userId") + val userId: String, + + /** + * Scopes the application requests when installed on a team. Organization-level and project-level scopes only. + */ + @SerializedName("installationScopes") + val installationScopes: List, + + /** + * URL users are redirected to after creating or updating an installation of this application. Empty for no redirect. + */ + @SerializedName("installationRedirectUrl") + val installationRedirectUrl: String, + + /** + * List of application secrets. + */ + @SerializedName("secrets") + val secrets: List, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "name" to name as Any, + "description" to description as Any, + "clientUri" to clientUri as Any, + "logoUri" to logoUri as Any, + "privacyPolicyUrl" to privacyPolicyUrl as Any, + "termsUrl" to termsUrl as Any, + "contacts" to contacts as Any, + "tagline" to tagline as Any, + "tags" to tags as Any, + "labels" to labels as Any, + "images" to images as Any, + "supportUrl" to supportUrl as Any, + "dataDeletionUrl" to dataDeletionUrl as Any, + "redirectUris" to redirectUris as Any, + "postLogoutRedirectUris" to postLogoutRedirectUris as Any, + "enabled" to enabled as Any, + "type" to type as Any, + "deviceFlow" to deviceFlow as Any, + "teamId" to teamId as Any, + "userId" to userId as Any, + "installationScopes" to installationScopes as Any, + "installationRedirectUrl" to installationRedirectUrl as Any, + "secrets" to secrets.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = App( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + name = map["name"] as String, + description = map["description"] as String, + clientUri = map["clientUri"] as String, + logoUri = map["logoUri"] as String, + privacyPolicyUrl = map["privacyPolicyUrl"] as String, + termsUrl = map["termsUrl"] as String, + contacts = map["contacts"] as List, + tagline = map["tagline"] as String, + tags = map["tags"] as List, + labels = map["labels"] as List, + images = map["images"] as List, + supportUrl = map["supportUrl"] as String, + dataDeletionUrl = map["dataDeletionUrl"] as String, + redirectUris = map["redirectUris"] as List, + postLogoutRedirectUris = map["postLogoutRedirectUris"] as List, + enabled = map["enabled"] as Boolean, + type = map["type"] as String, + deviceFlow = map["deviceFlow"] as Boolean, + teamId = map["teamId"] as String, + userId = map["userId"] as String, + installationScopes = map["installationScopes"] as List, + installationRedirectUrl = map["installationRedirectUrl"] as String, + secrets = (map["secrets"] as List>).map { AppSecret.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppKey.kt b/library/src/main/java/io/appwrite/models/AppKey.kt new file mode 100644 index 00000000..f59e8d50 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppKey.kt @@ -0,0 +1,93 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppKey + */ +data class AppKey( + /** + * App key ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * App key creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * App key update time in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application ID this app key belongs to. + */ + @SerializedName("appId") + val appId: String, + + /** + * App key secret. + */ + @SerializedName("secret") + val secret: String, + + /** + * Last few characters of the app key secret, used to help identify it. + */ + @SerializedName("hint") + val hint: String, + + /** + * ID of the user who created the app key. + */ + @SerializedName("createdById") + val createdById: String, + + /** + * Name of the user who created the app key. + */ + @SerializedName("createdByName") + val createdByName: String, + + /** + * Time the app key was last used for authentication in ISO 8601 format. Null if never used. + */ + @SerializedName("lastAccessedAt") + var lastAccessedAt: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "appId" to appId as Any, + "secret" to secret as Any, + "hint" to hint as Any, + "createdById" to createdById as Any, + "createdByName" to createdByName as Any, + "lastAccessedAt" to lastAccessedAt as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppKey( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + appId = map["appId"] as String, + secret = map["secret"] as String, + hint = map["hint"] as String, + createdById = map["createdById"] as String, + createdByName = map["createdByName"] as String, + lastAccessedAt = map["lastAccessedAt"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppKeyList.kt b/library/src/main/java/io/appwrite/models/AppKeyList.kt new file mode 100644 index 00000000..ea4b1cca --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppKeyList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App keys list + */ +data class AppKeyList( + /** + * Total number of keys that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of keys. + */ + @SerializedName("keys") + val keys: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "keys" to keys.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppKeyList( + total = (map["total"] as Number).toLong(), + keys = (map["keys"] as List>).map { AppKey.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppScope.kt b/library/src/main/java/io/appwrite/models/AppScope.kt new file mode 100644 index 00000000..4cc7fe95 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppScope.kt @@ -0,0 +1,61 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppScope + */ +data class AppScope( + /** + * Scope value as requested by apps. + */ + @SerializedName("value") + val value: String, + + /** + * Human-readable description of what the scope grants. + */ + @SerializedName("description") + val description: String, + + /** + * What the scope grants access to. One of `account`, `project`, or `organization`. Only `project` and `organization` scopes are installable. + */ + @SerializedName("type") + val type: String, + + /** + * Scope category, used to group scopes on consent and installation screens. + */ + @SerializedName("category") + val category: String, + + /** + * Whether the scope is deprecated. Deprecated scopes can still be requested but should not be offered for new grants. + */ + @SerializedName("deprecated") + val deprecated: Boolean, + +) { + fun toMap(): Map = mapOf( + "value" to value as Any, + "description" to description as Any, + "type" to type as Any, + "category" to category as Any, + "deprecated" to deprecated as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppScope( + value = map["value"] as String, + description = map["description"] as String, + type = map["type"] as String, + category = map["category"] as String, + deprecated = map["deprecated"] as Boolean, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppScopeList.kt b/library/src/main/java/io/appwrite/models/AppScopeList.kt new file mode 100644 index 00000000..cbcf91fb --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppScopeList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App scopes list + */ +data class AppScopeList( + /** + * Total number of scopes that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of scopes. + */ + @SerializedName("scopes") + val scopes: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "scopes" to scopes.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppScopeList( + total = (map["total"] as Number).toLong(), + scopes = (map["scopes"] as List>).map { AppScope.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppSecret.kt b/library/src/main/java/io/appwrite/models/AppSecret.kt new file mode 100644 index 00000000..0349ffbd --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppSecret.kt @@ -0,0 +1,93 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppSecret + */ +data class AppSecret( + /** + * Secret ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Secret creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Secret update time in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application ID this secret belongs to. + */ + @SerializedName("appId") + val appId: String, + + /** + * Always empty. The application client secret is returned only once, in the response of the createSecret method. + */ + @SerializedName("secret") + val secret: String, + + /** + * Last few characters of the client secret, used to help identify it. + */ + @SerializedName("hint") + val hint: String, + + /** + * ID of the user who created the secret. + */ + @SerializedName("createdById") + val createdById: String, + + /** + * Name of the user who created the secret. + */ + @SerializedName("createdByName") + val createdByName: String, + + /** + * Time the secret was last used for authentication in ISO 8601 format. Null if never used. + */ + @SerializedName("lastAccessedAt") + var lastAccessedAt: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "appId" to appId as Any, + "secret" to secret as Any, + "hint" to hint as Any, + "createdById" to createdById as Any, + "createdByName" to createdByName as Any, + "lastAccessedAt" to lastAccessedAt as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppSecret( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + appId = map["appId"] as String, + secret = map["secret"] as String, + hint = map["hint"] as String, + createdById = map["createdById"] as String, + createdByName = map["createdByName"] as String, + lastAccessedAt = map["lastAccessedAt"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppSecretList.kt b/library/src/main/java/io/appwrite/models/AppSecretList.kt new file mode 100644 index 00000000..04d2ebb7 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppSecretList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * App secrets list + */ +data class AppSecretList( + /** + * Total number of secrets that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of secrets. + */ + @SerializedName("secrets") + val secrets: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "secrets" to secrets.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppSecretList( + total = (map["total"] as Number).toLong(), + secrets = (map["secrets"] as List>).map { AppSecret.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt b/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt new file mode 100644 index 00000000..098b7f19 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt @@ -0,0 +1,93 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * AppSecretPlaintext + */ +data class AppSecretPlaintext( + /** + * Secret ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Secret creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Secret update time in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Application ID this secret belongs to. + */ + @SerializedName("appId") + val appId: String, + + /** + * Application client secret. Returned only when the secret is created; subsequent reads always return an empty value. + */ + @SerializedName("secret") + val secret: String, + + /** + * Last few characters of the client secret, used to help identify it. + */ + @SerializedName("hint") + val hint: String, + + /** + * ID of the user who created the secret. + */ + @SerializedName("createdById") + val createdById: String, + + /** + * Name of the user who created the secret. + */ + @SerializedName("createdByName") + val createdByName: String, + + /** + * Time the secret was last used for authentication in ISO 8601 format. Null if never used. + */ + @SerializedName("lastAccessedAt") + var lastAccessedAt: String?, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "appId" to appId as Any, + "secret" to secret as Any, + "hint" to hint as Any, + "createdById" to createdById as Any, + "createdByName" to createdByName as Any, + "lastAccessedAt" to lastAccessedAt as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppSecretPlaintext( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + appId = map["appId"] as String, + secret = map["secret"] as String, + hint = map["hint"] as String, + createdById = map["createdById"] as String, + createdByName = map["createdByName"] as String, + lastAccessedAt = map["lastAccessedAt"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/AppsList.kt b/library/src/main/java/io/appwrite/models/AppsList.kt new file mode 100644 index 00000000..5dcff821 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/AppsList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Apps list + */ +data class AppsList( + /** + * Total number of apps that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of apps. + */ + @SerializedName("apps") + val apps: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "apps" to apps.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = AppsList( + total = (map["total"] as Number).toLong(), + apps = (map["apps"] as List>).map { App.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/InputFile.kt b/library/src/main/java/io/appwrite/models/InputFile.kt index a5b377aa..779da916 100644 --- a/library/src/main/java/io/appwrite/models/InputFile.kt +++ b/library/src/main/java/io/appwrite/models/InputFile.kt @@ -2,8 +2,6 @@ package io.appwrite.models import java.io.File import java.net.URLConnection -import java.nio.file.Files -import java.nio.file.Paths class InputFile private constructor() { @@ -17,9 +15,7 @@ class InputFile private constructor() { fun fromFile(file: File) = InputFile().apply { path = file.canonicalPath filename = file.name - mimeType = Files.probeContentType(Paths.get(file.canonicalPath)) - ?: URLConnection.guessContentTypeFromName(filename) - ?: "" + mimeType = URLConnection.guessContentTypeFromName(filename) ?: "" sourceType = "file" } diff --git a/library/src/main/java/io/appwrite/models/Oauth2Approve.kt b/library/src/main/java/io/appwrite/models/Oauth2Approve.kt new file mode 100644 index 00000000..e09ab9ea --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Approve.kt @@ -0,0 +1,29 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Approve + */ +data class Oauth2Approve( + /** + * URL the end user should be redirected to after the grant is approved, carrying the authorization `code` and/or `id_token` along with the original `state`. + */ + @SerializedName("redirectUrl") + val redirectUrl: String, + +) { + fun toMap(): Map = mapOf( + "redirectUrl" to redirectUrl as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Approve( + redirectUrl = map["redirectUrl"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt b/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt new file mode 100644 index 00000000..e0812f7e --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Authorize + */ +data class Oauth2Authorize( + /** + * OAuth2 grant ID. Set when the user must give explicit consent; pass it to the approve or reject endpoint. Empty when a redirect URL is returned instead. + */ + @SerializedName("grantId") + val grantId: String, + + /** + * URL the end user should be redirected to when the flow can complete without consent. Empty when consent is still required. + */ + @SerializedName("redirectUrl") + val redirectUrl: String, + +) { + fun toMap(): Map = mapOf( + "grantId" to grantId as Any, + "redirectUrl" to redirectUrl as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Authorize( + grantId = map["grantId"] as String, + redirectUrl = map["redirectUrl"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt b/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt new file mode 100644 index 00000000..58c7e5ee --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt @@ -0,0 +1,69 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Device Authorization + */ +data class Oauth2DeviceAuthorization( + /** + * Device verification code used by the client to poll the token endpoint. + */ + @SerializedName("device_code") + val device_code: String, + + /** + * Short code the end user enters on the verification page. + */ + @SerializedName("user_code") + val user_code: String, + + /** + * URL where the end user enters the user code. + */ + @SerializedName("verification_uri") + val verification_uri: String, + + /** + * Verification URL with the user code prefilled as a query parameter. + */ + @SerializedName("verification_uri_complete") + val verification_uri_complete: String, + + /** + * Lifetime of the device code and user code in seconds. + */ + @SerializedName("expires_in") + val expires_in: Long, + + /** + * Minimum polling interval for the token endpoint in seconds. + */ + @SerializedName("interval") + val interval: Long, + +) { + fun toMap(): Map = mapOf( + "device_code" to device_code as Any, + "user_code" to user_code as Any, + "verification_uri" to verification_uri as Any, + "verification_uri_complete" to verification_uri_complete as Any, + "expires_in" to expires_in as Any, + "interval" to interval as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2DeviceAuthorization( + device_code = map["device_code"] as String, + user_code = map["user_code"] as String, + verification_uri = map["verification_uri"] as String, + verification_uri_complete = map["verification_uri_complete"] as String, + expires_in = (map["expires_in"] as Number).toLong(), + interval = (map["interval"] as Number).toLong(), + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Grant.kt b/library/src/main/java/io/appwrite/models/Oauth2Grant.kt new file mode 100644 index 00000000..ef1a7c39 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Grant.kt @@ -0,0 +1,117 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Grant + */ +data class Oauth2Grant( + /** + * Grant ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Grant creation time in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Grant update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * ID of the user the grant belongs to. + */ + @SerializedName("userId") + val userId: String, + + /** + * ID of the OAuth2 client (app) the grant was requested for. + */ + @SerializedName("appId") + val appId: String, + + /** + * Requested OAuth2 scopes the user is being asked to consent to. + */ + @SerializedName("scopes") + val scopes: List, + + /** + * Requested RFC 8707 resource indicators the user is being asked to consent to. + */ + @SerializedName("resources") + val resources: List, + + /** + * Requested authorization_details the user is being asked to consent to, as a JSON string. Each entry has a `type` plus project-defined fields. + */ + @SerializedName("authorizationDetails") + val authorizationDetails: String, + + /** + * OIDC prompt directive the consent screen should honor. Space-separated list of: login, consent, select_account. + */ + @SerializedName("prompt") + val prompt: String, + + /** + * Redirect URI the user will be sent to after the flow completes. + */ + @SerializedName("redirectUri") + val redirectUri: String, + + /** + * Unix timestamp of when the user last authenticated. + */ + @SerializedName("authTime") + val authTime: Long, + + /** + * Grant expiration time in ISO 8601 format. + */ + @SerializedName("expire") + val expire: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "\$updatedAt" to updatedAt as Any, + "userId" to userId as Any, + "appId" to appId as Any, + "scopes" to scopes as Any, + "resources" to resources as Any, + "authorizationDetails" to authorizationDetails as Any, + "prompt" to prompt as Any, + "redirectUri" to redirectUri as Any, + "authTime" to authTime as Any, + "expire" to expire as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Grant( + id = map["\$id"] as String, + createdAt = map["\$createdAt"] as String, + updatedAt = map["\$updatedAt"] as String, + userId = map["userId"] as String, + appId = map["appId"] as String, + scopes = map["scopes"] as List, + resources = map["resources"] as List, + authorizationDetails = map["authorizationDetails"] as String, + prompt = map["prompt"] as String, + redirectUri = map["redirectUri"] as String, + authTime = (map["authTime"] as Number).toLong(), + expire = map["expire"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Organization.kt b/library/src/main/java/io/appwrite/models/Oauth2Organization.kt new file mode 100644 index 00000000..605916a0 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Organization.kt @@ -0,0 +1,29 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Organization + */ +data class Oauth2Organization( + /** + * Organization ID. + */ + @SerializedName("\$id") + val id: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Organization( + id = map["\$id"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt b/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt new file mode 100644 index 00000000..b90ee636 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 accessible organizations list + */ +data class Oauth2OrganizationList( + /** + * Total number of organizations that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of organizations. + */ + @SerializedName("organizations") + val organizations: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "organizations" to organizations.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2OrganizationList( + total = (map["total"] as Number).toLong(), + organizations = (map["organizations"] as List>).map { Oauth2Organization.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2PAR.kt b/library/src/main/java/io/appwrite/models/Oauth2PAR.kt new file mode 100644 index 00000000..94d175ee --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2PAR.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 PAR + */ +data class Oauth2PAR( + /** + * Authorization request handle to pass to the authorize endpoint. + */ + @SerializedName("request_uri") + val request_uri: String, + + /** + * Lifetime of the authorization request handle in seconds. + */ + @SerializedName("expires_in") + val expires_in: Long, + +) { + fun toMap(): Map = mapOf( + "request_uri" to request_uri as Any, + "expires_in" to expires_in as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2PAR( + request_uri = map["request_uri"] as String, + expires_in = (map["expires_in"] as Number).toLong(), + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Project.kt b/library/src/main/java/io/appwrite/models/Oauth2Project.kt new file mode 100644 index 00000000..eb085dce --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Project.kt @@ -0,0 +1,45 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Project + */ +data class Oauth2Project( + /** + * Project ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Region ID the project is deployed in. + */ + @SerializedName("region") + val region: String, + + /** + * API endpoint of the region the project is deployed in. Empty when the region has no public hostname configured. + */ + @SerializedName("endpoint") + val endpoint: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "region" to region as Any, + "endpoint" to endpoint as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Project( + id = map["\$id"] as String, + region = map["region"] as String, + endpoint = map["endpoint"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt b/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt new file mode 100644 index 00000000..551b9ee4 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 accessible projects list + */ +data class Oauth2ProjectList( + /** + * Total number of projects that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of projects. + */ + @SerializedName("projects") + val projects: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "projects" to projects.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2ProjectList( + total = (map["total"] as Number).toLong(), + projects = (map["projects"] as List>).map { Oauth2Project.from(map = it) }, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Reject.kt b/library/src/main/java/io/appwrite/models/Oauth2Reject.kt new file mode 100644 index 00000000..b23f308c --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Reject.kt @@ -0,0 +1,29 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Reject + */ +data class Oauth2Reject( + /** + * URL the end user should be redirected to after the grant is rejected, carrying an `access_denied` error. + */ + @SerializedName("redirectUrl") + val redirectUrl: String, + +) { + fun toMap(): Map = mapOf( + "redirectUrl" to redirectUrl as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Reject( + redirectUrl = map["redirectUrl"] as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Oauth2Token.kt b/library/src/main/java/io/appwrite/models/Oauth2Token.kt new file mode 100644 index 00000000..f36f7768 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Oauth2Token.kt @@ -0,0 +1,77 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * OAuth2 Token + */ +data class Oauth2Token( + /** + * OAuth2 access token. + */ + @SerializedName("access_token") + val access_token: String, + + /** + * OAuth2 token type. + */ + @SerializedName("token_type") + val token_type: String, + + /** + * Access token lifetime in seconds. + */ + @SerializedName("expires_in") + val expires_in: Long, + + /** + * OAuth2 refresh token. + */ + @SerializedName("refresh_token") + val refresh_token: String, + + /** + * Space-separated scopes granted to the access token. + */ + @SerializedName("scope") + val scope: String, + + /** + * Granted RFC 9396 authorization details as a JSON string. + */ + @SerializedName("authorization_details") + var authorization_details: String?, + + /** + * OpenID Connect ID token. Returned when the `openid` scope is granted. + */ + @SerializedName("id_token") + var id_token: String?, + +) { + fun toMap(): Map = mapOf( + "access_token" to access_token as Any, + "token_type" to token_type as Any, + "expires_in" to expires_in as Any, + "refresh_token" to refresh_token as Any, + "scope" to scope as Any, + "authorization_details" to authorization_details as Any?, + "id_token" to id_token as Any?, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Oauth2Token( + access_token = map["access_token"] as String, + token_type = map["token_type"] as String, + expires_in = (map["expires_in"] as Number).toLong(), + refresh_token = map["refresh_token"] as String, + scope = map["scope"] as String, + authorization_details = map["authorization_details"] as? String, + id_token = map["id_token"] as? String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/RealtimeModels.kt b/library/src/main/java/io/appwrite/models/RealtimeModels.kt index ed55bbb1..8d083ad0 100644 --- a/library/src/main/java/io/appwrite/models/RealtimeModels.kt +++ b/library/src/main/java/io/appwrite/models/RealtimeModels.kt @@ -39,14 +39,33 @@ data class RealtimeCallback( open class RealtimeResponse( val type: String, val data: Any? -) +) { + companion object { + fun from(map: Map) = RealtimeResponse( + type = map["type"] as? String ?: "", + data = map["data"], + ) + } +} data class RealtimeResponseEvent( val events: Collection, val channels: Collection, val timestamp: String, var payload: T -) +) { + companion object { + fun from(map: Map): RealtimeResponseEvent? { + val payload = map["payload"] ?: return null + return RealtimeResponseEvent( + events = (map["events"] as? List<*>)?.map { it.toString() } ?: emptyList(), + channels = (map["channels"] as? List<*>)?.map { it.toString() } ?: emptyList(), + timestamp = map["timestamp"]?.toString() ?: "", + payload = payload, + ) + } + } +} enum class RealtimeCode(val value: Int) { POLICY_VIOLATION(1008), diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index 17cdf820..8df38221 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -14,7 +14,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Account service allows you to authenticate and manage a user account. */ + * The Account service allows you to authenticate and manage a user account. + */ class Account(client: Client) : Service(client) { /** * Get the currently logged in user. @@ -1548,11 +1549,17 @@ class Account(client: Client) : Service(client) { /** * Get the list of active sessions across different devices for the currently logged in user. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.SessionList] */ - suspend fun listSessions(): io.appwrite.models.SessionList { + @JvmOverloads + suspend fun listSessions( + total: Boolean? = null, + ): io.appwrite.models.SessionList { val apiPath = "/account/sessions" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -1572,13 +1579,19 @@ class Account(client: Client) : Service(client) { } /** - * Delete all sessions from the user account and remove any sessions cookies from the end client. + * Delete all sessions from the user account and remove any sessions cookies from the end client. Pass `current` as false to keep the session making the request and sign out of every other session. * + * @param current Delete the current session too. Use false to sign out of every other session while staying signed in on this one. * @return [Any] */ - suspend fun deleteSessions(): Any { + @JvmOverloads + suspend fun deleteSessions( + current: Boolean? = null, + ): Any { val apiPath = "/account/sessions" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "current" to current, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "content-type" to "application/json", @@ -1621,22 +1634,26 @@ class Account(client: Client) : Service(client) { } /** - * Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. + * Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. Use the optional `duration` parameter to create a shorter session, for example when the user doesn't choose "remember me". It must be at least 60 seconds and cannot exceed the project maximum session length. * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * @param email User email. * @param password User password. Must be at least 8 chars. + * @param duration Session length in seconds. Minimum is 60 seconds, and it cannot exceed the project maximum session length. Defaults to the project maximum session length. * @return [io.appwrite.models.Session] */ + @JvmOverloads suspend fun createEmailPasswordSession( email: String, password: String, + duration: Long? = null, ): io.appwrite.models.Session { val apiPath = "/account/sessions/email" val apiParams = mutableMapOf( "email" to email, "password" to password, + "duration" to duration, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), @@ -1763,7 +1780,7 @@ class Account(client: Client) : Service(client) { * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, webflow, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. @@ -2247,7 +2264,7 @@ class Account(client: Client) : Service(client) { * * A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). * - * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom. + * @param provider OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, kakao, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tiktok, tradeshift, tradeshiftBox, twitch, webflow, wordpress, x, yahoo, yammer, yandex, zoho, zoom. * @param success URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param failure URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API. * @param scopes A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of 100 scopes are allowed, each 4096 characters long. diff --git a/library/src/main/java/io/appwrite/services/Apps.kt b/library/src/main/java/io/appwrite/services/Apps.kt new file mode 100644 index 00000000..20f0de13 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Apps.kt @@ -0,0 +1,881 @@ +package io.appwrite.services + +import android.net.Uri +import io.appwrite.Client +import io.appwrite.Service +import io.appwrite.exceptions.AppwriteException +import io.appwrite.extensions.classOf +import io.appwrite.models.* +import okhttp3.Cookie +import java.io.File + +/** + * The Apps service allows you to manage OAuth2 applications, their keys, secrets, scopes, and installations. + */ +class Apps(client: Client) : Service(client) { + /** + * List applications. + * + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppsList] + */ + @JvmOverloads + suspend fun list( + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppsList { + val apiPath = "/apps" + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppsList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppsList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppsList::class.java, + converter, + ) + } + + /** + * Create a new application. + * + * @param appId Application ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + * @param name Application name. + * @param redirectUris Redirect URIs. Each must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI (e.g. com.example.app:/oauth), and must not contain a fragment. + * @param description Application description shown to users during OAuth2 consent. + * @param clientUri Application homepage URL shown to users during OAuth2 consent. + * @param logoUri Application logo URL shown to users during OAuth2 consent. + * @param privacyPolicyUrl Application privacy policy URL shown to users during OAuth2 consent. + * @param termsUrl Application terms of service URL shown to users during OAuth2 consent. + * @param contacts Application support or security contact emails. Maximum of 100 contacts are allowed. + * @param tagline Application tagline shown to users during OAuth2 consent. + * @param tags Application tags shown to users during OAuth2 consent. Maximum of 100 tags are allowed, each up to 64 characters long. + * @param images Application image URLs shown to users during OAuth2 consent. Maximum of 100 images are allowed. + * @param supportUrl Application support URL shown to users during OAuth2 consent. + * @param dataDeletionUrl Application data deletion URL shown to users during OAuth2 consent. + * @param postLogoutRedirectUris Post-logout redirect URIs for OpenID Connect RP-Initiated Logout. Each must be an https URL, an http loopback URL, or a private-use scheme URI, and must not contain a fragment. After ending the user session, the logout endpoint only redirects to URIs in this list. + * @param enabled Is application enabled? + * @param type OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. + * @param deviceFlow Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. + * @param teamId Team unique ID. + * @return [io.appwrite.models.App] + */ + @JvmOverloads + suspend fun create( + appId: String, + name: String, + redirectUris: List, + description: String? = null, + clientUri: String? = null, + logoUri: String? = null, + privacyPolicyUrl: String? = null, + termsUrl: String? = null, + contacts: List? = null, + tagline: String? = null, + tags: List? = null, + images: List? = null, + supportUrl: String? = null, + dataDeletionUrl: String? = null, + postLogoutRedirectUris: List? = null, + enabled: Boolean? = null, + type: String? = null, + deviceFlow: Boolean? = null, + teamId: String? = null, + ): io.appwrite.models.App { + val apiPath = "/apps" + val apiParams = mutableMapOf( + "appId" to appId, + "name" to name, + "description" to description, + "clientUri" to clientUri, + "logoUri" to logoUri, + "privacyPolicyUrl" to privacyPolicyUrl, + "termsUrl" to termsUrl, + "contacts" to contacts, + "tagline" to tagline, + "tags" to tags, + "images" to images, + "supportUrl" to supportUrl, + "dataDeletionUrl" to dataDeletionUrl, + "redirectUris" to redirectUris, + "postLogoutRedirectUris" to postLogoutRedirectUris, + "enabled" to enabled, + "type" to type, + "deviceFlow" to deviceFlow, + "teamId" to teamId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * List scopes an application can request when installed on a team. + * + * @return [io.appwrite.models.AppScopeList] + */ + suspend fun listInstallationScopes(): io.appwrite.models.AppScopeList { + val apiPath = "/apps/scopes/installations" + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppScopeList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppScopeList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppScopeList::class.java, + converter, + ) + } + + /** + * List scopes an application can request during the OAuth2 flow. + * + * @return [io.appwrite.models.AppScopeList] + */ + suspend fun listOAuth2Scopes(): io.appwrite.models.AppScopeList { + val apiPath = "/apps/scopes/oauth2" + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppScopeList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppScopeList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppScopeList::class.java, + converter, + ) + } + + /** + * Get an application by its unique ID. + * + * @param appId Application unique ID. + * @return [io.appwrite.models.App] + */ + suspend fun get( + appId: String, + ): io.appwrite.models.App { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * Update an application by its unique ID. + * + * @param appId Application unique ID. + * @param name Application name. + * @param description Application description shown to users during OAuth2 consent. + * @param clientUri Application homepage URL shown to users during OAuth2 consent. + * @param logoUri Application logo URL shown to users during OAuth2 consent. + * @param privacyPolicyUrl Application privacy policy URL shown to users during OAuth2 consent. + * @param termsUrl Application terms of service URL shown to users during OAuth2 consent. + * @param contacts Application support or security contact emails. Maximum of 100 contacts are allowed. + * @param tagline Application tagline shown to users during OAuth2 consent. + * @param tags Application tags shown to users during OAuth2 consent. Maximum of 100 tags are allowed, each up to 64 characters long. + * @param images Application image URLs shown to users during OAuth2 consent. Maximum of 100 images are allowed. + * @param supportUrl Application support URL shown to users during OAuth2 consent. + * @param dataDeletionUrl Application data deletion URL shown to users during OAuth2 consent. + * @param enabled Is application enabled? + * @param redirectUris Redirect URIs. Each must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI (e.g. com.example.app:/oauth), and must not contain a fragment. + * @param postLogoutRedirectUris Post-logout redirect URIs for OpenID Connect RP-Initiated Logout. Each must be an https URL, an http loopback URL, or a private-use scheme URI, and must not contain a fragment. After ending the user session, the logout endpoint only redirects to URIs in this list. + * @param type OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. + * @param deviceFlow Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. + * @param installationScopes Scopes the application requests when installed on a team. Only scopes allowed by the project's OAuth2 server installation scopes configuration are accepted; use the list installation scopes endpoint to discover available values. Maximum of 100 scopes are allowed. + * @param installationRedirectUrl URL users are redirected to after creating or updating an installation of this application. Must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI, and must not contain a fragment. Leave empty for no redirect. + * @return [io.appwrite.models.App] + */ + @JvmOverloads + suspend fun update( + appId: String, + name: String, + description: String? = null, + clientUri: String? = null, + logoUri: String? = null, + privacyPolicyUrl: String? = null, + termsUrl: String? = null, + contacts: List? = null, + tagline: String? = null, + tags: List? = null, + images: List? = null, + supportUrl: String? = null, + dataDeletionUrl: String? = null, + enabled: Boolean? = null, + redirectUris: List? = null, + postLogoutRedirectUris: List? = null, + type: String? = null, + deviceFlow: Boolean? = null, + installationScopes: List? = null, + installationRedirectUrl: String? = null, + ): io.appwrite.models.App { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "name" to name, + "description" to description, + "clientUri" to clientUri, + "logoUri" to logoUri, + "privacyPolicyUrl" to privacyPolicyUrl, + "termsUrl" to termsUrl, + "contacts" to contacts, + "tagline" to tagline, + "tags" to tags, + "images" to images, + "supportUrl" to supportUrl, + "dataDeletionUrl" to dataDeletionUrl, + "enabled" to enabled, + "redirectUris" to redirectUris, + "postLogoutRedirectUris" to postLogoutRedirectUris, + "type" to type, + "deviceFlow" to deviceFlow, + "installationScopes" to installationScopes, + "installationRedirectUrl" to installationRedirectUrl, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * Delete an application by its unique ID. + * + * @param appId Application unique ID. + * @return [Any] + */ + suspend fun delete( + appId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. + * + * @param appId Application unique ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppInstallationList] + */ + @JvmOverloads + suspend fun listInstallations( + appId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppInstallationList { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/installations" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallationList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallationList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallationList::class.java, + converter, + ) + } + + /** + * Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. + * + * @param appId Application unique ID. + * @param installationId Installation unique ID. + * @return [io.appwrite.models.AppInstallation] + */ + suspend fun getInstallation( + appId: String, + installationId: String, + ): io.appwrite.models.AppInstallation { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } + val apiPath = ("/apps/{appId}/installations/{installationId}" + .replace("{appId}", appId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppInstallation = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppInstallation.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppInstallation::class.java, + converter, + ) + } + + /** + * Delete an installation of an application by its unique ID. Requires a caller with update access to the app. Previously issued installation access tokens are revoked. + * + * @param appId Application unique ID. + * @param installationId Installation unique ID. + * @return [Any] + */ + suspend fun deleteInstallation( + appId: String, + installationId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } + val apiPath = ("/apps/{appId}/installations/{installationId}" + .replace("{appId}", appId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. + * + * @param appId Application unique ID. + * @param installationId Installation unique ID. + * @return [io.appwrite.models.Oauth2Token] + */ + suspend fun createInstallationToken( + appId: String, + installationId: String, + ): io.appwrite.models.Oauth2Token { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (installationId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"installationId\"") + } + val apiPath = ("/apps/{appId}/installations/{installationId}/tokens" + .replace("{appId}", appId) + .replace("{installationId}", installationId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Token.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Token::class.java, + converter, + ) + } + + /** + * List app keys for an application. + * + * @param appId Application unique ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppKeyList] + */ + @JvmOverloads + suspend fun listKeys( + appId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppKeyList { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/keys" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppKeyList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppKeyList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppKeyList::class.java, + converter, + ) + } + + /** + * Create a new app key for an application. App keys carry no scopes; send one in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header to list the application's installations and create installation access tokens. + * + * @param appId Application unique ID. + * @return [io.appwrite.models.AppKey] + */ + suspend fun createKey( + appId: String, + ): io.appwrite.models.AppKey { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/keys" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppKey = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppKey.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppKey::class.java, + converter, + ) + } + + /** + * Get an app key by its unique ID. + * + * @param appId Application unique ID. + * @param keyId App key unique ID. + * @return [io.appwrite.models.AppKey] + */ + suspend fun getKey( + appId: String, + keyId: String, + ): io.appwrite.models.AppKey { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (keyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"keyId\"") + } + val apiPath = ("/apps/{appId}/keys/{keyId}" + .replace("{appId}", appId) + .replace("{keyId}", keyId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppKey = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppKey.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppKey::class.java, + converter, + ) + } + + /** + * Delete an app key by its unique ID. + * + * @param appId Application unique ID. + * @param keyId App key unique ID. + * @return [Any] + */ + suspend fun deleteKey( + appId: String, + keyId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (keyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"keyId\"") + } + val apiPath = ("/apps/{appId}/keys/{keyId}" + .replace("{appId}", appId) + .replace("{keyId}", keyId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * List client secrets for an application. + * + * @param appId Application unique ID. + * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.AppSecretList] + */ + @JvmOverloads + suspend fun listSecrets( + appId: String, + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.AppSecretList { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/secrets" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "queries" to queries, + "total" to total, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppSecretList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppSecretList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppSecretList::class.java, + converter, + ) + } + + /** + * Create a new client secret for an application. + * + * @param appId Application unique ID. + * @return [io.appwrite.models.AppSecretPlaintext] + */ + suspend fun createSecret( + appId: String, + ): io.appwrite.models.AppSecretPlaintext { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/secrets" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppSecretPlaintext = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppSecretPlaintext.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppSecretPlaintext::class.java, + converter, + ) + } + + /** + * Get an application client secret by its unique ID. + * + * @param appId Application unique ID. + * @param secretId Secret unique ID. + * @return [io.appwrite.models.AppSecret] + */ + suspend fun getSecret( + appId: String, + secretId: String, + ): io.appwrite.models.AppSecret { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (secretId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"secretId\"") + } + val apiPath = ("/apps/{appId}/secrets/{secretId}" + .replace("{appId}", appId) + .replace("{secretId}", secretId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.AppSecret = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.AppSecret.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.AppSecret::class.java, + converter, + ) + } + + /** + * Delete an application client secret by its unique ID. + * + * @param appId Application unique ID. + * @param secretId Secret unique ID. + * @return [Any] + */ + suspend fun deleteSecret( + appId: String, + secretId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + if (secretId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"secretId\"") + } + val apiPath = ("/apps/{appId}/secrets/{secretId}" + .replace("{appId}", appId) + .replace("{secretId}", secretId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Transfer an application to another team by its unique ID. + * + * @param appId Application unique ID. + * @param teamId Team ID of the team to transfer application to. + * @return [io.appwrite.models.App] + */ + suspend fun updateTeam( + appId: String, + teamId: String, + ): io.appwrite.models.App { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/team" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf( + "teamId" to teamId, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.App = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.App.from(map = it as Map) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.App::class.java, + converter, + ) + } + + /** + * Revoke all tokens for an application by its unique ID. + * + * @param appId Application unique ID. + * @return [Any] + */ + suspend fun deleteTokens( + appId: String, + ): Any { + if (appId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"appId\"") + } + val apiPath = ("/apps/{appId}/tokens" + .replace("{appId}", appId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } +} diff --git a/library/src/main/java/io/appwrite/services/Avatars.kt b/library/src/main/java/io/appwrite/services/Avatars.kt index cce2d3da..3b11d165 100644 --- a/library/src/main/java/io/appwrite/services/Avatars.kt +++ b/library/src/main/java/io/appwrite/services/Avatars.kt @@ -12,7 +12,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars. */ + * The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars. + */ class Avatars(client: Client) : Service(client) { /** * You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET /account/sessions](https://appwrite.io/docs/references/cloud/client-web/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings. @@ -240,7 +241,7 @@ class Avatars(client: Client) : Service(client) { } /** - * Returns the best available profile photo for a user. The endpoint tries each source in priority order and returns the first successful result: OAuth2 identity photo, Gravatar, Libravatar, Appwrite Initials, built-in static fallback. + * Returns the best available profile photo for a user. The endpoint tries each source in priority order and returns the first successful result: a custom uploaded photo (see avatars.updatePhoto), OAuth2 identity photo, Gravatar, Libravatar, Appwrite Initials, built-in static fallback. * * Passing `userId` — `current()` for the authenticated user — resolves the photo from everything known about that user: identity photos, email, and name. An explicit `emailHash` or `name` then overrides just that value, and the user's remaining sources stay in the chain. Without `userId`, passing `emailHash` and/or `name` resolves the avatar from those values alone: the hash is looked up on Gravatar and Libravatar, the name is rendered as initials, and the session user stays out of the chain so their own photo never shadows the avatar being asked for. When nothing is passed, the photo resolves for the currently authenticated user. Emails are only ever accepted pre-hashed, so no address ends up in a URL. * @@ -289,6 +290,82 @@ class Avatars(client: Client) : Service(client) { ) } + /** + * Update the profile photo of the currently authenticated user. The uploaded image takes priority over every other photo source, including OAuth2 identity photos, Gravatar, and Libravatar. Updating an already customized photo replaces it. The image must be at most 5MB and is sent in a single request. + * + * @param file Binary image file of at most 5MB. Allowed file types are png, jpg, jpeg, and webp. + * @return [io.appwrite.models.Account] + */ + suspend fun updatePhoto( + file: InputFile, + nestedType: Class, + onProgress: ((UploadProgress) -> Unit)? = null + ): io.appwrite.models.Account { + val apiPath = "/avatars/photo" + val apiParams = mutableMapOf( + "file" to file, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "multipart/form-data", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Account = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Account.from(map = it as Map, nestedType) + } + val idParamName: String? = null + val paramName = "file" + return client.chunkedUpload( + apiPath, + apiHeaders, + apiParams, + responseType = classOf(), + converter, + paramName = paramName, + idParamName = idParamName, + onProgress = onProgress, + ) + } + + /** + * Update the profile photo of the currently authenticated user. The uploaded image takes priority over every other photo source, including OAuth2 identity photos, Gravatar, and Libravatar. Updating an already customized photo replaces it. The image must be at most 5MB and is sent in a single request. + * + * @param file Binary image file of at most 5MB. Allowed file types are png, jpg, jpeg, and webp. + * @return [io.appwrite.models.Account] + */ + @Throws(AppwriteException::class) + suspend fun updatePhoto( + file: InputFile, + onProgress: ((UploadProgress) -> Unit)? = null + ): io.appwrite.models.Account> = updatePhoto( + file, + nestedType = classOf(), + onProgress = onProgress + ) + + /** + * Delete the profile photo of the currently authenticated user and store the built-in static placeholder in its place. The placeholder is the user's photo from then on, so it takes priority over every other photo source — OAuth2 identity photos, Gravatar, Libravatar, and initials — until a new photo is uploaded with avatars.updatePhoto. + * + * @return [Any] + */ + suspend fun deletePhoto(): Any { + val apiPath = "/avatars/photo" + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "DELETE", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + /** * Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image. * @@ -334,7 +411,7 @@ class Avatars(client: Client) : Service(client) { * When width and height are specified, the image is resized accordingly. If both dimensions are 0, the API provides an image at original size. If dimensions are not specified, the default viewport size is 1280x720px. * * @param url Website URL which you want to capture. - * @param headers HTTP headers to send with the browser request. Defaults to empty. + * @param headers HTTP headers to send with the browser request. Only Accept and Accept-Language are allowed. Defaults to empty. * @param viewportWidth Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280. * @param viewportHeight Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720. * @param scale Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1. diff --git a/library/src/main/java/io/appwrite/services/Databases.kt b/library/src/main/java/io/appwrite/services/Databases.kt index fcc85678..8b3b8215 100644 --- a/library/src/main/java/io/appwrite/services/Databases.kt +++ b/library/src/main/java/io/appwrite/services/Databases.kt @@ -10,12 +10,14 @@ import okhttp3.Cookie import java.io.File /** - * The Databases service allows you to create structured collections of documents, query and filter lists of documents */ + * The Databases service allows you to create structured collections of documents, query and filter lists of documents + */ class Databases(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @Deprecated( @@ -25,10 +27,12 @@ class Databases(client: Client) : Service(client) { @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/databases/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/main/java/io/appwrite/services/DocumentsDB.kt b/library/src/main/java/io/appwrite/services/DocumentsDB.kt index fb7cd720..9c403245 100644 --- a/library/src/main/java/io/appwrite/services/DocumentsDB.kt +++ b/library/src/main/java/io/appwrite/services/DocumentsDB.kt @@ -10,21 +10,25 @@ import okhttp3.Cookie import java.io.File /** - * */ + * + */ class DocumentsDB(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/documentsdb/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/main/java/io/appwrite/services/Functions.kt b/library/src/main/java/io/appwrite/services/Functions.kt index 57821693..0417207c 100644 --- a/library/src/main/java/io/appwrite/services/Functions.kt +++ b/library/src/main/java/io/appwrite/services/Functions.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Functions Service allows you view, create and manage your Cloud Functions. */ + * The Functions Service allows you view, create and manage your Cloud Functions. + */ class Functions(client: Client) : Service(client) { /** * Get a list of all the current user function execution logs. You can use the query params to filter your results. diff --git a/library/src/main/java/io/appwrite/services/Graphql.kt b/library/src/main/java/io/appwrite/services/Graphql.kt index c1a776d9..dba5986f 100644 --- a/library/src/main/java/io/appwrite/services/Graphql.kt +++ b/library/src/main/java/io/appwrite/services/Graphql.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The GraphQL API allows you to query and mutate your Appwrite server using GraphQL. */ + * The GraphQL API allows you to query and mutate your Appwrite server using GraphQL. + */ class Graphql(client: Client) : Service(client) { /** * Execute a GraphQL mutation. diff --git a/library/src/main/java/io/appwrite/services/Locale.kt b/library/src/main/java/io/appwrite/services/Locale.kt index 9c08a68d..a6eb364c 100644 --- a/library/src/main/java/io/appwrite/services/Locale.kt +++ b/library/src/main/java/io/appwrite/services/Locale.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Locale service allows you to customize your app based on your users' location. */ + * The Locale service allows you to customize your app based on your users' location. + */ class Locale(client: Client) : Service(client) { /** * Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language. @@ -43,11 +44,17 @@ class Locale(client: Client) : Service(client) { /** * List of all locale codes in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.LocaleCodeList] */ - suspend fun listCodes(): io.appwrite.models.LocaleCodeList { + @JvmOverloads + suspend fun listCodes( + total: Boolean? = null, + ): io.appwrite.models.LocaleCodeList { val apiPath = "/locale/codes" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -69,11 +76,17 @@ class Locale(client: Client) : Service(client) { /** * List of all continents. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.ContinentList] */ - suspend fun listContinents(): io.appwrite.models.ContinentList { + @JvmOverloads + suspend fun listContinents( + total: Boolean? = null, + ): io.appwrite.models.ContinentList { val apiPath = "/locale/continents" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -95,11 +108,17 @@ class Locale(client: Client) : Service(client) { /** * List of all countries. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.CountryList] */ - suspend fun listCountries(): io.appwrite.models.CountryList { + @JvmOverloads + suspend fun listCountries( + total: Boolean? = null, + ): io.appwrite.models.CountryList { val apiPath = "/locale/countries" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -121,11 +140,17 @@ class Locale(client: Client) : Service(client) { /** * List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.CountryList] */ - suspend fun listCountriesEU(): io.appwrite.models.CountryList { + @JvmOverloads + suspend fun listCountriesEU( + total: Boolean? = null, + ): io.appwrite.models.CountryList { val apiPath = "/locale/countries/eu" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -147,11 +172,17 @@ class Locale(client: Client) : Service(client) { /** * List of all countries phone codes. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.PhoneList] */ - suspend fun listCountriesPhones(): io.appwrite.models.PhoneList { + @JvmOverloads + suspend fun listCountriesPhones( + total: Boolean? = null, + ): io.appwrite.models.PhoneList { val apiPath = "/locale/countries/phones" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -173,11 +204,17 @@ class Locale(client: Client) : Service(client) { /** * List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.CurrencyList] */ - suspend fun listCurrencies(): io.appwrite.models.CurrencyList { + @JvmOverloads + suspend fun listCurrencies( + total: Boolean? = null, + ): io.appwrite.models.CurrencyList { val apiPath = "/locale/currencies" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", @@ -199,11 +236,17 @@ class Locale(client: Client) : Service(client) { /** * List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language. * + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.LanguageList] */ - suspend fun listLanguages(): io.appwrite.models.LanguageList { + @JvmOverloads + suspend fun listLanguages( + total: Boolean? = null, + ): io.appwrite.models.LanguageList { val apiPath = "/locale/languages" - val apiParams = mutableMapOf() + val apiParams = mutableMapOf( + "total" to total, + ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), "accept" to "application/json", diff --git a/library/src/main/java/io/appwrite/services/Messaging.kt b/library/src/main/java/io/appwrite/services/Messaging.kt index 0e816f78..e0e36b13 100644 --- a/library/src/main/java/io/appwrite/services/Messaging.kt +++ b/library/src/main/java/io/appwrite/services/Messaging.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.). */ + * The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.). + */ class Messaging(client: Client) : Service(client) { /** * Create a new subscriber. diff --git a/library/src/main/java/io/appwrite/services/Oauth2.kt b/library/src/main/java/io/appwrite/services/Oauth2.kt new file mode 100644 index 00000000..030f4cef --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Oauth2.kt @@ -0,0 +1,680 @@ +package io.appwrite.services + +import android.net.Uri +import io.appwrite.Client +import io.appwrite.Service +import io.appwrite.exceptions.AppwriteException +import io.appwrite.extensions.classOf +import io.appwrite.models.* +import okhttp3.Cookie +import java.io.File + +/** + * The OAuth2 service allows you to authorize apps and issue standards-based OAuth2 and OpenID Connect tokens. + */ +class Oauth2(client: Client) : Service(client) { + /** + * Approve an OAuth2 grant after the user gives consent. Returns the `redirectUrl` the end user should be sent to. The consent screen may optionally pass enriched `authorization_details` to record the concrete resources the user selected. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param grantId Grant ID made during authorization, provided to consent screen in URL search params. + * @param authorizationDetails Enriched `authorization_details` the user consented to, replacing what the client requested. Each entry must use a `type` the project accepts. Optional; omit to keep the originally requested details. + * @param scope Space-separated scopes the user consented to. Must be a subset of the scopes originally requested; identity scopes such as `openid` are always retained. Optional; omit to keep the originally requested scopes. + * @return [io.appwrite.models.Oauth2Approve] + */ + @JvmOverloads + suspend fun approve( + grantId: String, + authorizationDetails: String? = null, + scope: String? = null, + ): io.appwrite.models.Oauth2Approve { + val apiPath = ("/oauth2/{project_id}/approve" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "grant_id" to grantId, + "authorization_details" to authorizationDetails, + "scope" to scope, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Approve = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Approve.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Approve::class.java, + converter, + ) + } + + /** + * Begin the OAuth2 authorization flow. When called without a session, the user is redirected to the consent screen without grant ID. When called with a session, the redirect URL includes param for grant ID. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param redirectUri Redirect URI where visitor will be redirected after authorization, whether successful or not. + * @param responseType OAuth2 / OIDC response type. One of `code` (Authorization Code Flow), `id_token` (Implicit Flow, OIDC login only), or `code id_token` (Hybrid Flow). + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + * @param state OAuth2 state. You receive this back in the redirect URI. + * @param nonce OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + * @param codeChallenge PKCE code challenge. Required when OAuth2 app is public. + * @param codeChallengeMethod PKCE code challenge method. Required when OAuth2 app is public. + * @param prompt OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + * @param maxAge OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @param requestUri OAuth2 authorization request handle returned by the pushed authorization request endpoint. + * @return [io.appwrite.models.Oauth2Authorize] + */ + @JvmOverloads + suspend fun authorize( + clientId: String? = null, + redirectUri: String? = null, + responseType: String? = null, + scope: String? = null, + state: String? = null, + nonce: String? = null, + codeChallenge: String? = null, + codeChallengeMethod: String? = null, + prompt: String? = null, + maxAge: Long? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + requestUri: String? = null, + ): io.appwrite.models.Oauth2Authorize { + val apiPath = ("/oauth2/{project_id}/authorize" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "redirect_uri" to redirectUri, + "response_type" to responseType, + "scope" to scope, + "state" to state, + "nonce" to nonce, + "code_challenge" to codeChallenge, + "code_challenge_method" to codeChallengeMethod, + "prompt" to prompt, + "max_age" to maxAge, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + "request_uri" to requestUri, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Authorize = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Authorize.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Authorize::class.java, + converter, + ) + } + + /** + * Begin the OAuth2 authorization flow. When called without a session, the user is redirected to the consent screen without grant ID. When called with a session, the redirect URL includes param for grant ID. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param redirectUri Redirect URI where visitor will be redirected after authorization, whether successful or not. + * @param responseType OAuth2 / OIDC response type. One of `code` (Authorization Code Flow), `id_token` (Implicit Flow, OIDC login only), or `code id_token` (Hybrid Flow). + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + * @param state OAuth2 state. You receive this back in the redirect URI. + * @param nonce OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + * @param codeChallenge PKCE code challenge. Required when OAuth2 app is public. + * @param codeChallengeMethod PKCE code challenge method. Required when OAuth2 app is public. + * @param prompt OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + * @param maxAge OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @param requestUri OAuth2 authorization request handle returned by the pushed authorization request endpoint. + * @return [io.appwrite.models.Oauth2Authorize] + */ + @JvmOverloads + suspend fun authorizePost( + clientId: String? = null, + redirectUri: String? = null, + responseType: String? = null, + scope: String? = null, + state: String? = null, + nonce: String? = null, + codeChallenge: String? = null, + codeChallengeMethod: String? = null, + prompt: String? = null, + maxAge: Long? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + requestUri: String? = null, + ): io.appwrite.models.Oauth2Authorize { + val apiPath = ("/oauth2/{project_id}/authorize" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "redirect_uri" to redirectUri, + "response_type" to responseType, + "scope" to scope, + "state" to state, + "nonce" to nonce, + "code_challenge" to codeChallenge, + "code_challenge_method" to codeChallengeMethod, + "prompt" to prompt, + "max_age" to maxAge, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + "request_uri" to requestUri, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Authorize = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Authorize.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Authorize::class.java, + converter, + ) + } + + /** + * Start the OAuth2 Device Authorization Grant. Returns the device code, user code, verification URL, expiration, and polling interval. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @return [io.appwrite.models.Oauth2DeviceAuthorization] + */ + @JvmOverloads + suspend fun createDeviceAuthorization( + clientId: String? = null, + scope: String? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + ): io.appwrite.models.Oauth2DeviceAuthorization { + val apiPath = ("/oauth2/{project_id}/device_authorization" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "scope" to scope, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2DeviceAuthorization = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2DeviceAuthorization.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2DeviceAuthorization::class.java, + converter, + ) + } + + /** + * Exchange a device flow user code for an OAuth2 grant. The authenticated user is bound to the pending grant. Pass the returned grant ID to the get grant endpoint to render the consent screen, then to the approve or reject endpoint to complete the flow. + * + * @param userCode User code displayed on the device. + * @return [io.appwrite.models.Oauth2Grant] + */ + suspend fun createGrant( + userCode: String, + ): io.appwrite.models.Oauth2Grant { + val apiPath = ("/oauth2/{project_id}/grants" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "user_code" to userCode, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Grant = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Grant.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Grant::class.java, + converter, + ) + } + + /** + * Get an OAuth2 grant by its ID. Used by the consent screen to display the details of the authorization the user is being asked to approve. A grant can only be read by the user it belongs to, or by server SDK. + * + * @param grantId Grant ID made during authorization, provided to consent screen in URL search params. + * @return [io.appwrite.models.Oauth2Grant] + */ + suspend fun getGrant( + grantId: String, + ): io.appwrite.models.Oauth2Grant { + if (grantId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"grantId\"") + } + val apiPath = ("/oauth2/{project_id}/grants/{grant_id}" + .replace("{project_id}", client.config["project"].orEmpty()) + .replace("{grant_id}", grantId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Grant = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Grant.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Grant::class.java, + converter, + ) + } + + /** + * OpenID Connect RP-Initiated Logout. Ends the user session and revokes the tokens issued to the app identified by the `id_token_hint`, then redirects the user to `post_logout_redirect_uri` when it matches a URI registered on the app. + * + * @param idTokenHint ID Token previously issued to the app, used as proof of the logout request. Required to end the session; signature and issuer are validated while expiry is ignored. + * @param logoutHint Hint about the user that is logging out. Accepted for OIDC compatibility. + * @param clientId OAuth2 client ID. When both `client_id` and `id_token_hint` are provided, they must identify the same app. + * @param postLogoutRedirectUri URI to redirect the user to after logout. Must exactly match a URI registered in the app's `postLogoutRedirectUris`. + * @param state Opaque value passed back unchanged in the `state` query param of the post-logout redirect. + * @param uiLocales Preferred languages for any logout UI, as space-separated BCP47 tags. Accepted for OIDC compatibility. + * @return [Any] + */ + @JvmOverloads + suspend fun logout( + idTokenHint: String? = null, + logoutHint: String? = null, + clientId: String? = null, + postLogoutRedirectUri: String? = null, + state: String? = null, + uiLocales: String? = null, + ): Any { + val apiPath = ("/oauth2/{project_id}/logout" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "id_token_hint" to idTokenHint, + "logout_hint" to logoutHint, + "client_id" to clientId, + "post_logout_redirect_uri" to postLogoutRedirectUri, + "state" to state, + "ui_locales" to uiLocales, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * OpenID Connect RP-Initiated Logout. Ends the user session and revokes the tokens issued to the app identified by the `id_token_hint`, then redirects the user to `post_logout_redirect_uri` when it matches a URI registered on the app. + * + * @param idTokenHint ID Token previously issued to the app, used as proof of the logout request. Required to end the session; signature and issuer are validated while expiry is ignored. + * @param logoutHint Hint about the user that is logging out. Accepted for OIDC compatibility. + * @param clientId OAuth2 client ID. When both `client_id` and `id_token_hint` are provided, they must identify the same app. + * @param postLogoutRedirectUri URI to redirect the user to after logout. Must exactly match a URI registered in the app's `postLogoutRedirectUris`. + * @param state Opaque value passed back unchanged in the `state` query param of the post-logout redirect. + * @param uiLocales Preferred languages for any logout UI, as space-separated BCP47 tags. Accepted for OIDC compatibility. + * @return [Any] + */ + @JvmOverloads + suspend fun logoutPost( + idTokenHint: String? = null, + logoutHint: String? = null, + clientId: String? = null, + postLogoutRedirectUri: String? = null, + state: String? = null, + uiLocales: String? = null, + ): Any { + val apiPath = ("/oauth2/{project_id}/logout" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "id_token_hint" to idTokenHint, + "logout_hint" to logoutHint, + "client_id" to clientId, + "post_logout_redirect_uri" to postLogoutRedirectUri, + "state" to state, + "ui_locales" to uiLocales, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * List the organizations the OAuth2 access token can access. Resolves the token's `organization` authorization details, expanding the `*` wildcard into the concrete set of organizations the user can see. + * + * @param limit Maximum number of organizations to return. Between 1 and 5000. + * @param offset Number of organizations to skip before returning results. Used for pagination. + * @param search Search term to filter your list results. Max length: 256 chars. + * @return [io.appwrite.models.Oauth2OrganizationList] + */ + @JvmOverloads + suspend fun listOrganizations( + limit: Long? = null, + offset: Long? = null, + search: String? = null, + ): io.appwrite.models.Oauth2OrganizationList { + val apiPath = ("/oauth2/{project_id}/organizations" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "limit" to limit, + "offset" to offset, + "search" to search, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2OrganizationList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2OrganizationList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2OrganizationList::class.java, + converter, + ) + } + + /** + * Store an OAuth2 authorization request server-side and receive a short-lived request_uri handle for the authorize endpoint. + * + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param redirectUri Redirect URI where visitor will be redirected after authorization, whether successful or not. + * @param responseType OAuth2 / OIDC response type. + * @param scope Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + * @param state OAuth2 state. You receive this back in the redirect URI. + * @param nonce OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + * @param codeChallenge PKCE code challenge. Required when OAuth2 app is public. + * @param codeChallengeMethod PKCE code challenge method. Required when OAuth2 app is public. + * @param prompt OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + * @param maxAge OIDC max_age parameter for customization of consent screen. + * @param authorizationDetails Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @return [io.appwrite.models.Oauth2PAR] + */ + @JvmOverloads + suspend fun createPAR( + clientId: String, + redirectUri: String, + responseType: String, + scope: String? = null, + state: String? = null, + nonce: String? = null, + codeChallenge: String? = null, + codeChallengeMethod: String? = null, + prompt: String? = null, + maxAge: Long? = null, + authorizationDetails: String? = null, + resource: String? = null, + audience: String? = null, + ): io.appwrite.models.Oauth2PAR { + val apiPath = ("/oauth2/{project_id}/par" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "client_id" to clientId, + "redirect_uri" to redirectUri, + "response_type" to responseType, + "scope" to scope, + "state" to state, + "nonce" to nonce, + "code_challenge" to codeChallenge, + "code_challenge_method" to codeChallengeMethod, + "prompt" to prompt, + "max_age" to maxAge, + "authorization_details" to authorizationDetails, + "resource" to resource, + "audience" to audience, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2PAR = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2PAR.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2PAR::class.java, + converter, + ) + } + + /** + * List the projects the OAuth2 access token can access. Resolves the token's `project` authorization details, expanding the `*` wildcard into the concrete set of projects the user can see. + * + * @param limit Maximum number of projects to return. Between 1 and 5000. + * @param offset Number of projects to skip before returning results. Used for pagination. + * @param search Search term to filter your list results. Max length: 256 chars. + * @return [io.appwrite.models.Oauth2ProjectList] + */ + @JvmOverloads + suspend fun listProjects( + limit: Long? = null, + offset: Long? = null, + search: String? = null, + ): io.appwrite.models.Oauth2ProjectList { + val apiPath = ("/oauth2/{project_id}/projects" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "limit" to limit, + "offset" to offset, + "search" to search, + ) + val apiHeaders = mutableMapOf( + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2ProjectList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2ProjectList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2ProjectList::class.java, + converter, + ) + } + + /** + * Reject an OAuth2 grant when the user denies consent. Returns the `redirectUrl` the end user should be sent to with an `access_denied` error. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + * + * @param grantId Grant ID made during authorization, provided to consent screen in URL search params. + * @return [io.appwrite.models.Oauth2Reject] + */ + suspend fun reject( + grantId: String, + ): io.appwrite.models.Oauth2Reject { + val apiPath = ("/oauth2/{project_id}/reject" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "grant_id" to grantId, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Reject = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Reject.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Reject::class.java, + converter, + ) + } + + /** + * Revoke an OAuth2 access token or refresh token. + * + * @param token The access or refresh token to revoke. + * @param tokenTypeHint Type of token to revoke (access_token or refresh_token). + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param clientSecret OAuth2 client secret. Required for confidential apps; omitted for public apps. + * @return [Any] + */ + @JvmOverloads + suspend fun revoke( + token: String, + tokenTypeHint: String? = null, + clientId: String? = null, + clientSecret: String? = null, + ): Any { + val apiPath = ("/oauth2/{project_id}/revoke" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "token" to token, + "token_type_hint" to tokenTypeHint, + "client_id" to clientId, + "client_secret" to clientSecret, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } + + /** + * Exchange an OAuth2 authorization code, refresh token, or device code for access and refresh tokens. + * + * @param grantType OAuth2 grant type. Can be one of: `authorization_code`, `refresh_token`, `urn:ietf:params:oauth:grant-type:device_code`. + * @param code Authorization code to be exchanged for access and refresh tokens. Required for `authorization_code` grant type. + * @param refreshToken Refresh token to be exchanged for a new access and refresh tokens. Required for `refresh_token` grant type. + * @param deviceCode Device code obtained from the device authorization endpoint. Required for `urn:ietf:params:oauth:grant-type:device_code` grant type. + * @param clientId OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + * @param clientSecret OAuth2 client secret. Required for confidential apps. + * @param codeVerifier PKCE code verifier. Required for public apps. + * @param redirectUri Redirect URI. Required for `authorization_code` grant type. + * @param resource RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + * @param audience Compatibility alias for a single OAuth2 resource indicator URI. + * @return [io.appwrite.models.Oauth2Token] + */ + @JvmOverloads + suspend fun createToken( + grantType: String, + code: String? = null, + refreshToken: String? = null, + deviceCode: String? = null, + clientId: String? = null, + clientSecret: String? = null, + codeVerifier: String? = null, + redirectUri: String? = null, + resource: String? = null, + audience: String? = null, + ): io.appwrite.models.Oauth2Token { + val apiPath = ("/oauth2/{project_id}/token" + .replace("{project_id}", client.config["project"].orEmpty()) + ) + val apiParams = mutableMapOf( + "grant_type" to grantType, + "code" to code, + "refresh_token" to refreshToken, + "device_code" to deviceCode, + "client_id" to clientId, + "client_secret" to clientSecret, + "code_verifier" to codeVerifier, + "redirect_uri" to redirectUri, + "resource" to resource, + "audience" to audience, + ) + val apiHeaders = mutableMapOf( + "content-type" to "application/json", + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Oauth2Token = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Oauth2Token.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Oauth2Token::class.java, + converter, + ) + } +} diff --git a/library/src/main/java/io/appwrite/services/Organization.kt b/library/src/main/java/io/appwrite/services/Organization.kt index 264a356e..00ed08a9 100644 --- a/library/src/main/java/io/appwrite/services/Organization.kt +++ b/library/src/main/java/io/appwrite/services/Organization.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Organization service allows you to manage organization-level projects. */ + * The Organization service allows you to manage organization-level projects. + */ class Organization(client: Client) : Service(client) { /** * List app installations on the organization. Any organization member can read installations. diff --git a/library/src/main/java/io/appwrite/services/Presences.kt b/library/src/main/java/io/appwrite/services/Presences.kt index a28448e4..31181a0c 100644 --- a/library/src/main/java/io/appwrite/services/Presences.kt +++ b/library/src/main/java/io/appwrite/services/Presences.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Presences service allows you to track and manage real-time user presence in your project. */ + * The Presences service allows you to track and manage real-time user presence in your project. + */ class Presences(client: Client) : Service(client) { /** * List presence logs. Expired entries are filtered out automatically. diff --git a/library/src/main/java/io/appwrite/services/Push.kt b/library/src/main/java/io/appwrite/services/Push.kt new file mode 100644 index 00000000..6fab40ad --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Push.kt @@ -0,0 +1,765 @@ +package io.appwrite.services + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.hivemq.client.mqtt.datatypes.MqttQos +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import io.appwrite.Client +import io.appwrite.ResolvedTopic +import io.appwrite.Service +import io.appwrite.Topic +import io.appwrite.exceptions.AppwriteException +import java.net.URI +import java.util.UUID +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ConcurrentHashMap + +private const val LOG_TAG = "AppwritePush" + +/** + * Handle for a live subscription created with [Push.subscribe]. + * + * Unlike the realtime service's bare unsubscribe callable, a push subscription carries + * per-subscription state (background delivery, notification title), so [Push.subscribe] + * returns this handle: [unsubscribe] drops it (and closes the connection once the last one + * is gone) and [update] toggles its options live without resubscribing. + */ +class PushSubscription internal constructor( + private val unsubscribeAction: () -> Unit, + private val updateAction: (Boolean?, String?, Boolean?) -> Unit, +) { + /** Drop this subscription. Closes the connection once the last one is gone. */ + fun unsubscribe() = unsubscribeAction() + + /** + * Live-update this subscription's background delivery, notification title and/or + * `retry` (QoS). Only the arguments you pass change; the rest stay as they + * were. Changing `retry` re-subscribes this subscription's topics at the new QoS. + */ + @JvmOverloads + fun update(background: Boolean? = null, title: String? = null, retry: Boolean? = null) = + updateAction(background, title, retry) +} + +/** + * Appwrite native push service — the realtime analog delivered over an MQTT broker, + * without FCM or APNS. + * + * While the app process is alive the connection stays open and each message is handed to + * your callback. A subscription can also opt into background delivery (`background = true`): + * it is saved, and keeps delivering while the app is backgrounded, after the process is + * killed, and after a reboot or an app update, until it is unsubscribed or [close] is called + * (e.g. on sign-out). No service is kept running for it: a scheduled job and alarm wake the + * process every 15 to 60 seconds to reconnect, and the broker keeps the messages sent in + * between and replays them (for `retry = true`). While the process is alive, messages arrive + * immediately. [setForeground] adds a foreground service for immediate delivery even after a kill + * and during Doze. Messages no in-app callback receives (the process was started in the + * background) go to a declared [PushReceiver], and are posted as a notification that opens + * the app unless it handles them. While any background subscription exists, all subscriptions + * share its connection. + * + * Background delivery reconnects with the credential saved at subscribe time, so use a session + * (`Client.setSession`) for it: a JWT expires after minutes. A refused credential stops + * background delivery and reaches [onError], also when it was registered later. + * + * The credential is read off the client — set a JWT or session on it + * (`Client.setJWT` / `Client.setSession`), the same way every other service reads auth. + * + * The connection always keeps its session (clean start off) so the broker can redeliver + * missed messages, and reliability is chosen per subscription via [subscribe]'s + * `retry`. The broker owns the replay window server-side (no MQTT session-expiry + * is set). The client id defaults to one per user and install, so replay resumes across + * restarts; `Client.setPushClientId(...)` overrides it. TLS (and skipping cert verification via a `?tlsInsecure=true` query flag) is + * derived from the endpoint, so the whole connection is described in one place. + * + * ```kotlin + * val client = Client().setEndpoint("...").setProject("...").setJWT(jwt) + * val push = Push(client, context) + * val sub = push.subscribe("user/123/#") { message -> + * println("${'$'}{message.topic}: ${'$'}{message.data}") + * } + * // ...later + * sub.unsubscribe() + * + * // Or, with no topic, the signed-in user's own topic (`users/`): + * push.subscribe { message -> println(message.data) } + * ``` + */ +class Push( + client: Client, + context: Context, +) : Service(client) { + + // The broker location is taken from Client.setPushEndpoint() when set (e.g. + // "mqtts://host:8883"), otherwise from the regular endpoint. + private val pushEndpoint: String = client.endpointPush ?: client.endpoint + + // When no explicit push endpoint is set, default to a `push.` host on the regular endpoint + // (mirroring how realtime derives from the endpoint, on the push subdomain). + private val host: String = + (runCatching { URI(pushEndpoint).host }.getOrNull() ?: "localhost") + .let { if (client.endpointPush != null) it else "push.$it" } + + // Secure by default: TLS on when the endpoint scheme is secure (https/wss/mqtts/ssl), so + // the credential in the CONNECT packet is not sent in the clear. + private val tls: Boolean = + runCatching { URI(pushEndpoint).scheme }.getOrNull()?.lowercase() in listOf("https", "wss", "mqtts", "ssl") + + // Take the port from the push endpoint only (a regular http/https endpoint's port is the + // API port, not the broker's); otherwise fall back to the MQTT default for the TLS mode. + private val port: Int = + client.endpointPush?.let { runCatching { URI(it).port.takeIf { p -> p != -1 } }.getOrNull() } + ?: (if (tls) 8883 else 1883) + + // Skip TLS cert verification (self-signed brokers / testing) via a query flag on the push + // endpoint, e.g. "mqtts://host:8883?tlsInsecure=true" — so the whole connection is + // described by one URL, not a second config knob. + private val tlsInsecure: Boolean = + client.endpointPush?.let { + runCatching { + val query = URI(it).query ?: "" + query.split("&").any { p -> p == "tlsInsecure=true" || p == "tlsInsecure=1" } + }.getOrDefault(false) + } ?: false + + // Read on the MQTT client's threads to tell a closed/replaced client's failures apart. + @Volatile + private var mqtt: Mqtt5AsyncClient? = null + + private var connectedConfig: PushConfig? = null + private val connectLock = Any() + + @Volatile + private var connectionEpoch = 0 + + // Local id -> subscription (filter + callback + per-sub options). The id is never sent to + // the broker; it only lets the same topic carry more than one callback. HiveMQ callbacks + // look their subscription up by id on delivery, because a callback stays attached to its + // filter until the filter itself is unsubscribed, which waits for the filter's last sub. + private val subscriptions = ConcurrentHashMap() + + // Whether the connection currently lives in the process-wide background host (its own MQTT + // client) rather than here. It moves there while any subscription, live or saved by an + // earlier run, wants background, and back here once none do — one connection either way. + private var backgroundActive = false + + // For background delivery: its saved state, scheduled runs, service and notifications. + private val appContext: Context = context.applicationContext + + init { + // Resume saved background delivery now, instead of at the next scheduled run. + PushBackground.resume(appContext) + } + + private var onOpen: (() -> Unit)? = null + private var onClose: (() -> Unit)? = null + private var onError: ((Throwable) -> Unit)? = null + + private class Sub( + val filter: String, + val callback: (PushMessage) -> Unit, + var background: Boolean, + var title: String?, + var retry: Boolean, + ) { + @Volatile + var client: Mqtt5AsyncClient? = null + } + + // The broker subscription for a filter uses the highest QoS any subscription on it wants, + // so a QoS-0 subscription never downgrades a QoS-1 one sharing the filter. + private fun effectiveQos(filter: String): MqttQos = + if (subscriptions.values.any { it.filter == filter && it.retry }) { + MqttQos.AT_LEAST_ONCE + } else { + MqttQos.AT_MOST_ONCE + } + + /** Register a callback invoked when the connection opens. */ + fun onOpen(callback: () -> Unit): Push { + onOpen = callback + return this + } + + /** Register a callback invoked when the connection closes. */ + fun onClose(callback: () -> Unit): Push { + onClose = callback + return this + } + + /** + * Register a callback invoked on every error the push service hits: a failed connect or + * automatic reconnect, a lost connection, a broker DISCONNECT, a rejected SUBSCRIBE or + * UNSUBSCRIBE, a foreground-service failure, and the failure of a [subscribe], + * [PushSubscription.update] or [PushSubscription.unsubscribe] call (which still throws to + * its caller). When the broker explains a refused connection or a DISCONNECT with an MQTT 5 + * Reason String, that is the error's message. + * + * Errors outside a call (on the MQTT client's or the foreground service's threads) are never + * thrown, so they cannot crash the app: with no callback registered they are logged. A + * failing call still throws to its caller. + */ + fun onError(callback: (Throwable) -> Unit): Push { + onError = callback + PushBackground.errorHandler = { deliverBackgroundError(it) } + // A refused credential that stopped background delivery while no onError was registered. + PushStore.takeStoppedError(appContext)?.let { report(AppwriteException(it)) } + return this + } + + /** + * Run background delivery in a foreground service (with a quiet ongoing + * notification), for immediate delivery even after the process is killed and during Doze, + * instead of only the scheduled runs. Saved, so it applies after restarts too; call it from + * the foreground (Android 12+ refuses to start the service from the background). Apps that + * never enable it can remove the service from their merged manifest. It only affects + * `background = true` subscriptions; `background = false` ones still deliver only while the + * app process is alive. + */ + fun setForeground(enabled: Boolean): Push { + PushBackground.setForeground(appContext, enabled) + return this + } + + /** + * Subscribe to the signed-in user's own topic, `users/` — "messages for me" + * without knowing any topic, e.g. `push.subscribe { message -> ... }`. + * + * The user id is read locally from the credential set on the client: the `userId` claim of + * the JWT (`Client.setJWT`), else the `id` in the session secret (`Client.setSession`). + * Throws when neither yields one. Like FCM/APNs delivery, it defaults to [background] `true` + * (keeps delivering while the app is backgrounded or closed) and [retry] + * `true` (QoS 1); pass either to override. Otherwise the options and the returned + * [PushSubscription] behave exactly as when subscribing to that topic; see the + * `List` overload. + */ + @JvmOverloads + fun subscribe( + background: Boolean = true, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription { + val userId = reporting { resolvePushUserId(client) } + return subscribe(Topic.path(listOf("users", userId)), background, title, retry, callback) + } + + /** + * Subscribe to a single topic. Returns a [PushSubscription] handle. + * + * Request the `POST_NOTIFICATIONS` runtime permission with [requestNotificationPermission] + * before subscribing with `background = true` on Android 13+. + */ + @JvmOverloads + fun subscribe( + topic: String, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = + subscribe(listOf(topic), background, title, retry, callback) + + /** + * Subscribe to a single topic built with [io.appwrite.Topic], e.g. + * `push.subscribe(Topic.path(listOf("user", userId)).any()) { message -> ... }`. + * Returns a [PushSubscription] handle; see the `List` overload for the options. + */ + @JvmOverloads + fun subscribe( + topic: Topic, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = + subscribe(listOf(topic.toString()), background, title, retry, callback) + + /** + * Subscribe to a single topic ended with `all()`, e.g. + * `push.subscribe(Topic.path(listOf("user", userId)).all()) { message -> ... }`. + * Returns a [PushSubscription] handle; see the `List` overload for the options. + */ + @JvmOverloads + fun subscribe( + topic: ResolvedTopic, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = + subscribe(listOf(topic.toString()), background, title, retry, callback) + + /** + * Subscribe to a list mixing raw strings and topics built with [io.appwrite.Topic], e.g. + * `push.subscribe(listOf("news", Topic.path(listOf("chat")).any())) { message -> ... }`. + * Each element must be a `String`, `Topic` or `ResolvedTopic` (Kotlin has no common type for + * them, so this is checked at runtime). Returns a [PushSubscription] handle; see the + * `List` overload for the options. + */ + @JvmOverloads + @JvmName("subscribeTopics") + fun subscribe( + topics: List, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription { + val filters = topics.mapIndexed { index, topic -> + require(topic is String || topic is Topic || topic is ResolvedTopic) { + "topics[$index] must be a String, Topic or ResolvedTopic, got ${topic::class.simpleName}" + } + topic.toString() + } + return subscribe(filters, background, title, retry, callback) + } + + /** + * Subscribe to one or more topics. Returns a [PushSubscription] handle. + * + * Topics are MQTT filters: pass raw strings, or build them with [io.appwrite.Topic] + * through the `Topic` overloads, e.g. `push.subscribe(Topic.path(listOf("user")).any()) { ... }`. + * + * For a foreground subscription this blocks until the broker has acknowledged every + * SUBSCRIBE (SUBACK): the broker only starts routing a topic once its subscription is + * registered, so publishing before the ack races the message ahead of the subscription + * and it is dropped (no retained replay on MQTT). + * + * Set [background] to keep receiving while the app is backgrounded or closed, also after the + * process is killed or the device restarts, and post a notification per matching message + * with [title] (defaulting to the message topic); see the class docs. [retry] chooses this + * subscription's QoS + * (`true` -> QoS 1 default, `false` -> QoS 0). Toggle any of these later with + * [PushSubscription.update]. + * + * Note: a `background` subscription hands the connection to the background host, which + * connects and subscribes asynchronously and retries failures on its next run — so it + * returns once the host has been handed the subscription, **not** after SUBACK (the SUBACK + * guarantee above holds for foreground subscriptions). [onOpen]/[onClose] observe only the + * in-process (foreground) connection, not the background one; [onError] observes both. + */ + @JvmOverloads + fun subscribe( + topics: List, + background: Boolean = false, + title: String? = null, + retry: Boolean = true, + callback: (PushMessage) -> Unit, + ): PushSubscription = reporting { + val ids = mutableListOf() + for (topic in topics) { + val id = UUID.randomUUID().toString() + ids.add(id) + subscriptions[id] = Sub(topic, callback, background, title, retry) + } + + // If any SUBSCRIBE fails, roll this call's registrations back and return to a + // consistent host for whatever subscriptions remain, then rethrow. + try { + if (hostedInBackground()) { + hostInBackground() + } else { + subscribeInProcess(ids) + } + } catch (e: Exception) { + for (id in ids) { + subscriptions.remove(id) + } + if (subscriptions.isEmpty()) { + releaseAll() + } else { + reconcile() + } + throw e + } + + PushSubscription( + { reporting { unsubscribeIds(ids) } }, + { bg, t, rd -> reporting { updateIds(ids, bg, t, rd) } }, + ) + } + + // Every error the service hits goes through here: to onError when one is registered. It is + // never thrown from here (these errors arrive on HiveMQ's or the service's threads, where a + // throw would crash the app), so with no onError it is logged, like Realtime does. + private fun report(error: Throwable) { + val callback = onError + if (callback == null) { + Log.e(LOG_TAG, "Push error", error) + return + } + try { + callback(error) + } catch (callbackError: Exception) { + Log.e(LOG_TAG, "Push onError threw", callbackError) + } + } + + // Run a call that fails by throwing to its caller: the failure also reaches onError when one + // is registered, and is still thrown to the caller, who can catch it. + private fun reporting(block: () -> T): T = try { + block() + } catch (e: Exception) { + if (onError != null) { + report(e) + } + throw e + } + + // report() for an error raised on the MQTT client's (or the background host's) thread. + private fun reportAsync(error: Throwable) = report(error) + + // A background host error, for its errorHandler: delivered to onError when one is registered. + // Returns false when none is, so the host logs it (and keeps a refusal for a later onError). + private fun deliverBackgroundError(error: Throwable): Boolean { + if (onError == null) { + return false + } + report(error) + return true + } + + // Report a fire-and-forget request's failure (e.g. a SUBACK/UNSUBACK error code) unless + // [client] was closed or replaced meanwhile, which fails its pending requests on purpose. + private fun CompletableFuture<*>.reportFailure(client: Mqtt5AsyncClient) { + whenComplete { _, error -> + if (error != null && mqtt === client) { + reportAsync(error) + } + } + } + + // In-process host: connect and register this call's new filters, blocking on each SUBACK. + private fun subscribeInProcess(ids: List) { + while (true) { + val (client, epoch) = synchronized(connectLock) { connect() to connectionEpoch } + try { + for (id in ids) { + val sub = subscriptions[id] ?: continue + if (sub.client === client) { + continue + } + client.subscribeWith() + .topicFilter(sub.filter) + .qos(effectiveQos(sub.filter)) + .callback { publish -> subscriptions[id]?.callback?.invoke(publish.toPushMessage()) } + .send() + .get() + sub.client = client + } + return + } catch (e: Exception) { + if (epoch == connectionEpoch) { + throw e + } + } + } + } + + // Relocate the connection into the background host and sync it with the current + // subscription set (filters, per-sub callbacks/titles), saving the background ones. + private fun hostInBackground() { + if (mqtt != null) { + mqtt?.disconnect() + mqtt = null + } + subscriptions.values.forEach { it.client = null } + PushBackground.errorHandler = { deliverBackgroundError(it) } + PushBackground.host(appContext, buildConfig(), currentListeners()) + backgroundActive = true + } + + // Bring the in-process connection up and (re)register every filter — used when the last + // background subscription goes away and the connection relocates back in-process. + private fun ensureInProcessSubscribed() { + val client = connect() + for ((id, sub) in subscriptions) { + if (sub.client === client) { + continue + } + client.subscribeWith() + .topicFilter(sub.filter) + .qos(effectiveQos(sub.filter)) + .callback { publish -> subscriptions[id]?.callback?.invoke(publish.toPushMessage()) } + .send() + .reportFailure(client) + sub.client = client + } + } + + // Re-subscribe a filter in-process at its new effective QoS. Unsubscribe first so the + // per-subscribe HiveMQ callbacks are not duplicated, then re-add one per sub on the filter. + private fun resubscribeFilterInProcess(filter: String) { + val client = mqtt ?: return + client.unsubscribeWith().topicFilter(filter).send().reportFailure(client) + val qos = effectiveQos(filter) + for ((id, sub) in subscriptions.filter { it.value.filter == filter }) { + client.subscribeWith() + .topicFilter(filter) + .qos(qos) + .callback { publish -> subscriptions[id]?.callback?.invoke(publish.toPushMessage()) } + .send() + .reportFailure(client) + sub.client = client + } + } + + // Ensure the correct host (in-process vs service) for whatever subscriptions remain, and + // sync it. Called after an unsubscribe/update may have flipped whether background is wanted. + // Only acts on a host change; an in-process update that changes QoS is applied by the + // caller (so an unchanged in-process host is not needlessly resubscribed). + private fun reconcile() { + if (subscriptions.isEmpty()) { + return + } + if (hostedInBackground()) { + // Relocate to (or re-sync) the background host, which applies added/removed/QoS-changed topics. + hostInBackground() + } else if (backgroundActive) { + // Relocate back in-process. + backgroundActive = false + PushBackground.release(appContext) + ensureInProcessSubscribed() + } + } + + private fun unsubscribeIds(ids: List) { + val wasService = backgroundActive + val maybeDowngrade = mutableSetOf() + for (id in ids) { + val entry = subscriptions.remove(id) + val filter = entry?.filter + if (entry?.retry == true && filter != null) { + maybeDowngrade.add(filter) + } + // In-process only: drop the broker filter once its last local callback is gone. + if (!wasService && filter != null && subscriptions.values.none { it.filter == filter }) { + mqtt?.let { client -> client.unsubscribeWith().topicFilter(filter).send().reportFailure(client) } + } + } + if (subscriptions.isEmpty()) { + releaseAll() + return + } + // A removed sub may have been the last background one (relocate in-process) or just + // change the service's topic/QoS set (the service re-sync applies the new QoS). + if (wasService) { + reconcile() + return + } + // In-process, a filter that lost its last QoS-1 sub but still has QoS-0 subs must be + // re-subscribed lower, or the broker keeps replaying to at-most-once subs. + for (filter in maybeDowngrade) { + if (subscriptions.values.any { it.filter == filter } && effectiveQos(filter) == MqttQos.AT_MOST_ONCE) { + resubscribeFilterInProcess(filter) + } + } + } + + private fun updateIds(ids: List, background: Boolean?, title: String?, retry: Boolean?) { + val changedFilters = mutableSetOf() + for (id in ids) { + val sub = subscriptions[id] ?: continue + if (background != null) { + sub.background = background + } + if (title != null) { + sub.title = title + } + if (retry != null && retry != sub.retry) { + sub.retry = retry + changedFilters.add(sub.filter) + } + } + val wasService = backgroundActive + // Handle any host change (relocation); a relocated host resubscribes everything fresh. + reconcile() + // Apply QoS changes to a host that did not relocate: the service re-sync above already + // covers the service host, so only the steady in-process host needs an explicit resub. + if (!hostedInBackground() && !wasService) { + for (filter in changedFilters) { + resubscribeFilterInProcess(filter) + } + } + } + + // The connection lives in the background host while any live subscription wants background, + // or an earlier run saved background subscriptions that are still delivered: two connections + // with the same client id would take over each other's broker session. + private fun hostedInBackground(): Boolean = + subscriptions.values.any { it.background } || PushBackground.hasSaved(appContext) + + private fun currentListeners(): List = + subscriptions.values.map { PushListener(setOf(it.filter), it.callback, it.background, it.title, it.retry) } + + /** + * Tear down the connection and drop all subscriptions, including the background ones saved + * by an earlier run, and stop background delivery. Call it on sign-out. + */ + fun close() { + mqtt?.disconnect() + mqtt = null + subscriptions.clear() + backgroundActive = false + PushBackground.stop(appContext) + } + + // The last live subscription is gone: close the connection, keeping any background + // subscriptions saved by an earlier run. + private fun releaseAll() { + mqtt?.disconnect() + mqtt = null + subscriptions.clear() + if (backgroundActive) { + backgroundActive = false + PushBackground.release(appContext) + } + } + + private fun connect(): Mqtt5AsyncClient = synchronized(connectLock) { + val config = buildConfig() + // Reuse the existing client even while it is mid automatic-reconnect. Building a second + // client here would give two clients the same stable id, so they would fight over the + // broker session and callbacks could stay attached to the abandoned one. + mqtt?.let { existing -> + if (config == connectedConfig) { + return existing + } + connectionEpoch++ + existing.disconnect() + mqtt = null + } + + // The listeners surface the lifecycle to onOpen/onClose and every connection error to + // onError, except a refused initial CONNECT: pushConnect throws that one, and the + // failing call reports it. + val client = buildPushClient( + config, + onConnected = { onOpen?.invoke() }, + onDisconnected = { onClose?.invoke() }, + onError = { reportAsync(it) }, + ) + mqtt = client + connectedConfig = config + + try { + client.pushConnect(config) + } catch (e: Exception) { + client.disconnect() + if (mqtt === client) { + mqtt = null + connectedConfig = null + } + throw e + } + + for ((id, sub) in subscriptions) { + if (sub.client == null || sub.client === client) { + continue + } + client.subscribeWith() + .topicFilter(sub.filter) + .qos(effectiveQos(sub.filter)) + .callback { publish -> subscriptions[id]?.callback?.invoke(publish.toPushMessage()) } + .send() + .reportFailure(client) + sub.client = client + } + + client + } + + private fun buildConfig(): PushConfig { + val (authMethod, credential) = resolvePushAuth(client) + return PushConfig( + host = host, + port = port, + tls = tls, + tlsInsecure = tlsInsecure, + // HiveMQ cannot connect with an empty id (it expects the broker to assign one), so + // when the app did not call Client.setPushClientId() derive a stable id per user and + // install. The broker keeps the replay position per user and client id, so each + // device resumes its own backlog across restarts, and two devices of one user do not + // take over each other's session. + clientId = client.pushClientId?.takeIf { it.isNotEmpty() } + ?: defaultPushClientId(appContext, authMethod, credential), + keepAlive = KEEP_ALIVE_SECONDS, + authMethod = authMethod, + credential = credential, + project = client.config["project"] ?: "", + ) + } + + companion object { + // Fixed keep-alive (PINGREQ interval) in seconds — not exposed as an option. + private const val KEEP_ALIVE_SECONDS = 60 + + /** The notification tap intent's extra holding the message topic. */ + const val EXTRA_TOPIC = PushBackground.EXTRA_TOPIC + + /** The notification tap intent's extra holding the message payload as a string. */ + const val EXTRA_PAYLOAD = PushBackground.EXTRA_PAYLOAD + + /** + * Request the `POST_NOTIFICATIONS` runtime permission (Android 13+). Call from an + * Activity before subscribing with `background = true`; on older versions it is a no-op. + */ + @JvmStatic + @JvmOverloads + fun requestNotificationPermission(activity: Activity, requestCode: Int = 9412) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return + } + if (ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + activity, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + requestCode, + ) + } + } + } +} + +/** Read the credential set on the client (via Client.setJWT / setSession). */ +internal fun resolvePushAuth(client: Client): Pair { + val jwt = client.config["jwt"] ?: client.config["jWT"] + val session = client.config["session"] + return when { + !jwt.isNullOrEmpty() -> "appwrite-jwt" to jwt + !session.isNullOrEmpty() -> "appwrite-session" to session + else -> throw AppwriteException( + "No credential set on the client; call Client.setJWT() or Client.setSession() first.", + ) + } +} + +/** + * The signed-in user's id for the topic-less [Push.subscribe], read locally in the same order as + * [resolvePushAuth]: the JWT's `userId` claim, else the `id` in the session secret (standard + * base64 of `{"id": ..., "secret": ...}`). Throws when neither yields one. + */ +internal fun resolvePushUserId(client: Client): String { + val jwt = client.config["jwt"] ?: client.config["jWT"] + val session = client.config["session"] + val userId = when { + !jwt.isNullOrEmpty() -> userIdFromJwt(jwt) + !session.isNullOrEmpty() -> userIdFromSession(session) + else -> "" + } + if (userId.isEmpty()) { + throw AppwriteException( + "subscribe() without a topic needs a signed-in user: set a JWT or session on the client", + ) + } + return userId +} diff --git a/library/src/main/java/io/appwrite/services/PushBackground.kt b/library/src/main/java/io/appwrite/services/PushBackground.kt new file mode 100644 index 00000000..52517344 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushBackground.kt @@ -0,0 +1,1009 @@ +package io.appwrite.services + +import android.app.AlarmManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.job.JobInfo +import android.app.job.JobScheduler +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.ConnectivityManager +import android.net.Network +import android.os.Build +import android.os.PowerManager +import android.os.SystemClock +import android.util.AtomicFile +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import com.hivemq.client.mqtt.MqttGlobalPublishFilter +import com.hivemq.client.mqtt.datatypes.MqttQos +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish +import io.appwrite.exceptions.AppwriteException +import org.json.JSONArray +import org.json.JSONObject +import java.io.File +import java.io.IOException +import java.util.UUID +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +private const val LOG_TAG = "AppwritePush" + +/** A background subscription saved across restarts: its filter, QoS choice and notification title. */ +internal data class PushEntry( + val filter: String, + val retry: Boolean, + val title: String?, +) + +/** + * The adaptive heartbeat: its interval, the current streak of failed heartbeats, and when the + * interval last shrank (wall-clock ms, as it must survive reboots). + */ +internal data class PushHeartbeat( + val intervalMs: Long, + val failures: Int = 0, + val shrunkAt: Long = 0L, +) + +/** + * The process-wide host of the background push connection, modelled on how socket push + * services survive on Android without keeping a service alive. + * + * The background subscriptions and their connection config are saved to private storage (see + * [PushStore]), so delivery survives the process being killed, a reboot and an app update. A + * job ([PushJobService]) that reschedules itself on every run, backed by an alarm + * ([PushAlarmReceiver]), wakes the process every 15 seconds (60 when the app may use exact + * alarms or is exempt from battery optimisation). Each run holds a short wakelock and + * reconnects when the connection is gone or the default network changed, sends a heartbeat + * when one is due, and re-arms the job. While the process is alive between runs the socket + * stays open and messages arrive immediately; while it is dead the broker keeps the backlog + * for this client id and replays it when the next run subscribes again. + * + * With foreground mode enabled ([Push.setForeground]) a foreground service ([PushService]) also keeps + * the process at foreground priority, so delivery is immediate after a kill and during Doze. + * + * Messages go to the live in-app callbacks that match them. When none match (the process was + * started in the background), a declared [PushReceiver] gets the message, and a notification + * is posted unless it returns true. + */ +internal object PushBackground { + const val CHANNEL_ID = "appwrite-push" + const val NOTIFICATION_ID = 1911 + const val ACTION_TICK = "io.appwrite.push.TICK" + const val ACTION_MESSAGE = "io.appwrite.push.MESSAGE" + const val NOTIFICATION_ICON = "io.appwrite.push.notification_icon" + const val EXTRA_TOPIC = "io.appwrite.push.TOPIC" + const val EXTRA_PAYLOAD = "io.appwrite.push.PAYLOAD" + + private const val SERVICE_CHANNEL_ID = "appwrite-push-service" + private const val JOB_ID = 0x50555348 + private const val INTERVAL_MS = 15_000L + private const val PRIVILEGED_INTERVAL_MS = 60_000L + private const val RESTART_DELAY_MS = 3_000L + // An upper bound covering a run's connect (20 s) and its requests (10 s each); the wakelock is + // released as soon as the run finishes. + private const val WAKE_LOCK_MS = 60_000L + private const val KEEP_ALIVE_SECONDS = 300 + + // The heartbeat interval: below the broker's 1.5 x keep-alive expiry, measured on a clock that + // counts deep sleep. Networks that drop idle connections sooner (carrier NATs) make heartbeats + // fail; after HEARTBEAT_FAILURES in a row the interval shrinks by a quarter, down to + // MIN_HEARTBEAT_MS, and returns to the default HEARTBEAT_RESET_MS after the last shrink, in + // case the device moved to a better network. + const val HEARTBEAT_MS = 180_000L + private const val MIN_HEARTBEAT_MS = 60_000L + private const val HEARTBEAT_FAILURES = 3 + private const val HEARTBEAT_RESET_MS = 3 * 24 * 60 * 60 * 1_000L + private const val REQUEST_TIMEOUT_SECONDS = 10L + private const val CONNECT_TIMEOUT_SECONDS = 20L + + // How long a message waits for a listener that acknowledges it itself (the React Native and + // Flutter bridges, once their callback has run) before it is acknowledged anyway, so one stuck + // callback cannot hold back the acknowledgements after it. + private const val ACK_TIMEOUT_SECONDS = 10L + + // Unsubscribing a filter nothing subscribes to is a harmless round trip the broker always + // acknowledges, so it proves the connection is alive. The broker ignores client PUBLISHes + // without a PUBACK, so a publish cannot serve as the heartbeat. + private const val HEARTBEAT_FILTER = "\$heartbeat" + + @Volatile + var channelName: String = "Notifications" + + @Volatile + var ongoingTitle: String = "Appwrite" + + // Reports errors to the Push instance that hosts its subscriptions here. Returns whether the + // app's onError received the error (false when it registered none). + @Volatile + var errorHandler: ((Throwable) -> Boolean)? = null + + @Volatile + var serviceRunning = false + + // Whether a refused service start was reported, so a job that retries it every run does not + // report the same failure every 15 seconds. + @Volatile + private var serviceStartReported = false + + // The live in-app subscriptions hosted here, each with its callback. Empty after the process + // was restarted in the background, until the app subscribes again. + val listeners = CopyOnWriteArrayList() + + // Guards the saved state below and its copy on disk. + private val lock = Any() + private var loaded = false + private var config: PushConfig? = null + + // Background subscriptions saved by an earlier run that no live subscription has taken over. + private var saved: List = emptyList() + + // Every MQTT call runs on this thread, in order, so the connection state below needs no lock. + private val worker = Executors.newSingleThreadScheduledExecutor { runnable -> + Thread(runnable, "AppwritePushBackground").apply { isDaemon = true } + } + private var mqtt: Mqtt5AsyncClient? = null + private var connectedConfig: PushConfig? = null + private var connectedNetwork: Network? = null + private var lastHeartbeat = 0L + private var heartbeatState: PushHeartbeat? = null + private val subscribed = mutableMapOf() + private var networkCallback: ConnectivityManager.NetworkCallback? = null + + // Worker thread only. While a caller waits for a converge (host with a completion), its + // failures go to that caller, which reports them, instead of onError. + private var waiting = false + private var failure: Throwable? = null + + /** + * Host [newListeners] (the app's live subscriptions) on the background connection with + * [newConfig], save the background ones, and arm the scheduled wake-ups. Saved subscriptions + * from an earlier run stay unless a live subscription on the same filter takes them over + * (with background on or off), or [newConfig] is for another client id (another user), whose + * saved subscriptions are dropped. + * + * With [done], it is called once the connection is up and every filter is subscribed (SUBACK), + * or with the failure, which is then not reported to onError: the caller reports it. + */ + fun host( + context: Context, + newConfig: PushConfig, + newListeners: List, + done: ((Throwable?) -> Unit)? = null, + ) { + val app = context.applicationContext + synchronized(lock) { + load(app) + if (config != null && config?.clientId != newConfig.clientId) { + saved = emptyList() + } + // A live subscription on a saved filter takes it over, whether or not it wants background: + // one that turned background off, or is later unsubscribed, must not leave the saved + // entry delivering in the background. + saved = saved.filter { entry -> newListeners.none { entry.filter in it.topics } } + config = newConfig + listeners.clear() + listeners.addAll(newListeners) + persist(app) + } + activate(app, done) + } + + /** + * Stop hosting the app's live subscriptions, keeping the saved ones from an earlier run. + * Shuts down when none remain. + */ + fun release(context: Context) { + val app = context.applicationContext + val active = synchronized(lock) { + listeners.clear() + persist(app) + isActive() + } + if (active) { + worker.execute { converge(app, heartbeat = false) } + } else { + shutdown(app) + } + } + + /** Stop background delivery completely and forget every saved subscription. */ + fun stop(context: Context) { + val app = context.applicationContext + synchronized(lock) { + listeners.clear() + config = null + saved = emptyList() + loaded = true + PushStore.clearState(app) + } + shutdown(app) + } + + /** Whether subscriptions saved by an earlier run are still delivered in the background. */ + fun hasSaved(context: Context): Boolean = synchronized(lock) { + load(context.applicationContext) + saved.isNotEmpty() + } + + /** Persist the foreground-mode choice and start or stop the foreground service to match. */ + fun setForeground(context: Context, enabled: Boolean) { + val app = context.applicationContext + PushStore.setForeground(app, enabled) + serviceStartReported = false + ensureService(app) + } + + /** Whether the foreground service should run: foreground mode is enabled and there is work to do. */ + fun wantsService(context: Context): Boolean { + val app = context.applicationContext + return PushStore.foreground(app) && synchronized(lock) { + load(app) + isActive() + } + } + + /** Resume saved background delivery when the app starts, without waiting for the next run. */ + fun resume(context: Context) { + val app = context.applicationContext + val active = synchronized(lock) { + load(app) + isActive() + } + if (active) { + tick(app) {} + } + } + + /** + * One scheduled run: under a wakelock, reconnect if needed, send a heartbeat when one is due, + * re-arm the next run, and call [done] once finished. Shuts down when nothing is saved. + */ + fun tick(context: Context, done: () -> Unit) { + val app = context.applicationContext + val wakeLock = acquireWakeLock(app) + worker.execute { + try { + val active = synchronized(lock) { + load(app) + isActive() + } + if (active) { + // Arm the next run first, so a run cut short (killed, or a slow broker outlasting + // the wakelock) still leaves the chain armed. A refused credential in converge() + // shuts down, cancelling it again. + schedule(app, INTERVAL_MS) + ensureService(app) + registerNetworkCallback(app) + converge(app, heartbeat = true) + } else { + shutdown(app) + } + } catch (e: Exception) { + report(e) + } finally { + releaseWakeLock(wakeLock) + done() + } + } + } + + /** Run once more shortly after the task was removed, so an OEM kill that follows is undone. */ + fun scheduleRestart(context: Context) { + schedule(context.applicationContext, RESTART_DELAY_MS, jobToo = false) + } + + /** + * Forget everything held in memory, as the process dying does, keeping only what is saved. + * For tests of what happens after the app's process was killed. + */ + internal fun dropProcessState(context: Context) { + synchronized(lock) { + listeners.clear() + config = null + saved = emptyList() + loaded = false + } + errorHandler = null + unregisterNetworkCallback(context.applicationContext) + worker.submit { disconnect() }.get() + } + + /** Report an error to the hosting Push instance's onError; logged when there is none. */ + fun report(error: Throwable): Boolean { + val delivered = try { + errorHandler?.invoke(error) ?: false + } catch (callbackError: Exception) { + Log.e(LOG_TAG, "Push onError threw", callbackError) + true + } + if (!delivered) { + Log.e(LOG_TAG, "Push background error", error) + } + return delivered + } + + // Must hold [lock]. + private fun load(context: Context) { + if (loaded) { + return + } + loaded = true + val state = PushStore.loadState(context) ?: return + config = state.first + saved = state.second + } + + // Must hold [lock]. + private fun isActive(): Boolean = config != null && (saved.isNotEmpty() || listeners.isNotEmpty()) + + // Must hold [lock]. Every background subscription, live or saved, one per filter and title. + // Subscriptions sharing a filter and title are saved once, with QoS 1 when any of them wants it. + private fun entries(): List = + (saved + listeners.filter { it.background }.flatMap { listener -> listener.topics.map { PushEntry(it, listener.retry, listener.title) } }) + .groupBy { it.filter to it.title } + .map { (key, group) -> PushEntry(key.first, group.any { it.retry }, key.second) } + + // Must hold [lock]. + private fun persist(context: Context) { + val current = config + val entries = entries() + if (current == null || entries.isEmpty()) { + PushStore.clearState(context) + } else { + PushStore.saveState(context, current, entries) + } + } + + // Must hold [lock]. The broker subscriptions wanted, at the highest QoS any subscription on a + // filter asks for. + private fun wanted(): Map { + val qos = linkedMapOf() + fun want(filter: String, retry: Boolean) { + if (retry) { + qos[filter] = MqttQos.AT_LEAST_ONCE + } else if (filter !in qos) { + qos[filter] = MqttQos.AT_MOST_ONCE + } + } + for (listener in listeners) { + listener.topics.forEach { want(it, listener.retry) } + } + saved.forEach { want(it.filter, it.retry) } + return qos + } + + private fun activate(context: Context, done: ((Throwable?) -> Unit)? = null) { + registerNetworkCallback(context) + schedule(context, INTERVAL_MS) + ensureService(context) + worker.execute { converge(context, heartbeat = false, done) } + } + + private fun shutdown(context: Context) { + cancelSchedule(context) + unregisterNetworkCallback(context) + if (serviceRunning) { + context.stopService(Intent(context, PushService::class.java)) + } + worker.execute { disconnect() } + } + + // Worker thread only. Bring the connection to the wanted state: reconnect when the config or + // the default network changed or a due heartbeat fails, then subscribe and unsubscribe the + // difference. With [done], failures go to it (see [waiting]) instead of onError. + private fun converge(context: Context, heartbeat: Boolean, done: ((Throwable?) -> Unit)? = null) { + waiting = done != null + failure = null + try { + convergeOnce(context, heartbeat) + } catch (e: Exception) { + fail(e) + } finally { + waiting = false + done?.invoke(failure) + } + } + + // Worker thread only. Record a failure for the waiting caller, or report it. + private fun fail(error: Throwable) { + if (failure == null) { + failure = error + } + if (!waiting) { + report(error) + } + } + + private fun convergeOnce(context: Context, heartbeat: Boolean) { + val (current, wanted) = synchronized(lock) { config to wanted() } + if (current == null || wanted.isEmpty()) { + disconnect() + return + } + var client = mqtt + if (client != null && (current != connectedConfig || networkChanged(context))) { + disconnect() + client = null + } + if (client != null && heartbeat && SystemClock.elapsedRealtime() - lastHeartbeat >= heartbeatInterval(context)) { + val alive = sendHeartbeat(client) + recordHeartbeat(context, alive) + if (alive) { + lastHeartbeat = SystemClock.elapsedRealtime() + } else { + disconnect() + client = null + } + } + if (client == null) { + client = connect(context, current) ?: return + } + for (filter in subscribed.keys - wanted.keys) { + request { client.unsubscribeWith().topicFilter(filter).send().get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) } + subscribed.remove(filter) + } + for ((filter, qos) in wanted) { + if (subscribed[filter] != qos && subscribe(client, filter, qos)) { + subscribed[filter] = qos + } + } + } + + // Worker thread only. Connect with [config], or report why not and return null. A refused + // credential stops background delivery: retrying it would only be refused again. + private fun connect(context: Context, config: PushConfig): Mqtt5AsyncClient? { + // A failed first connect of a waited-for converge goes to the waiting caller only. + val quiet = waiting + val connected = AtomicBoolean(false) + val client = buildPushClient( + config.copy(keepAlive = KEEP_ALIVE_SECONDS), + onError = { error -> + if (!quiet || connected.get()) { + report(error) + } + }, + onAuthRefused = { error -> worker.execute { refused(context, error) } }, + ) + // Registered before connecting, so the backlog the broker replays right after each + // SUBSCRIBE reaches the app however the subscription was made (including the ones the + // client restores itself after an automatic reconnect). Acknowledged by hand, once the + // message was delivered (see [deliver]). + client.publishes(MqttGlobalPublishFilter.ALL, { publish -> deliver(context, publish) }, true) + try { + client.sendPushConnect(config.copy(keepAlive = KEEP_ALIVE_SECONDS), CONNECT_TIMEOUT_SECONDS) + } catch (e: Exception) { + val cause = (e as? ExecutionException)?.cause ?: e + client.disconnect() + val error = connectError(cause) + when { + isAuthRefusal(cause) -> refused(context, error) + waiting -> fail(error) + // A network failure already reached onError through the client's disconnect + // listener; a refused CONNECT and a timeout did not. + cause is Mqtt5ConnAckException || cause is TimeoutException -> report(error) + } + return null + } + connected.set(true) + mqtt = client + connectedConfig = config + connectedNetwork = activeNetwork(context) + lastHeartbeat = SystemClock.elapsedRealtime() + subscribed.clear() + return client + } + + // Worker thread only. + private fun subscribe(client: Mqtt5AsyncClient, filter: String, qos: MqttQos): Boolean = request { + val subAck = client.subscribeWith().topicFilter(filter).qos(qos).send().get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) + val code = subAck.reasonCodes.firstOrNull { it.isError } + if (code != null) { + throw AppwriteException( + subAck.reasonString.map { it.toString() }.orElse("Subscribing to $filter was refused (reason $code)"), + ) + } + } + + // Worker thread only. The current heartbeat interval, back at the default once a shrink is + // old enough. + private fun heartbeatInterval(context: Context): Long { + val state = heartbeatState ?: PushStore.heartbeat(context).also { heartbeatState = it } + if (state.intervalMs < HEARTBEAT_MS && System.currentTimeMillis() - state.shrunkAt >= HEARTBEAT_RESET_MS) { + saveHeartbeat(context, PushHeartbeat(HEARTBEAT_MS)) + return HEARTBEAT_MS + } + return state.intervalMs + } + + // Worker thread only. Count a failed heartbeat (a connection the network dropped silently), + // shrinking the interval after HEARTBEAT_FAILURES in a row; a delivered one ends the streak. + private fun recordHeartbeat(context: Context, alive: Boolean) { + val state = heartbeatState ?: PushStore.heartbeat(context) + val next = when { + alive -> state.copy(failures = 0) + state.failures + 1 < HEARTBEAT_FAILURES -> state.copy(failures = state.failures + 1) + else -> PushHeartbeat(maxOf(MIN_HEARTBEAT_MS, state.intervalMs * 3 / 4), 0, System.currentTimeMillis()) + } + if (next != state) { + saveHeartbeat(context, next) + } + } + + private fun saveHeartbeat(context: Context, state: PushHeartbeat) { + heartbeatState = state + PushStore.setHeartbeat(context, state) + } + + // Worker thread only. + private fun sendHeartbeat(client: Mqtt5AsyncClient): Boolean = try { + client.unsubscribeWith().topicFilter(HEARTBEAT_FILTER).send().get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) + true + } catch (e: Exception) { + false + } + + // Run a broker request, recording or reporting its failure. Returns whether it succeeded. + private fun request(block: () -> Unit): Boolean = try { + block() + true + } catch (e: Exception) { + fail((e as? ExecutionException)?.cause ?: e) + false + } + + // Worker thread only. + private fun disconnect() { + mqtt?.disconnect() + mqtt = null + connectedConfig = null + connectedNetwork = null + subscribed.clear() + } + + // Worker thread only. The broker refused the credential: report it and stop. With no onError + // registered the message is also saved, and the next onError registration receives it. A + // waiting caller receives it instead. + private fun refused(context: Context, error: Throwable) { + if (waiting) { + fail(error) + } else if (!report(error)) { + PushStore.setStoppedError(context, error.message ?: "Push connection refused") + } + stop(context) + } + + // Deliver a message, then acknowledge it: right away, or once every listener that acknowledges + // messages itself has done so (at most ACK_TIMEOUT_SECONDS later). + private fun deliver(context: Context, publish: Mqtt5Publish) { + val acknowledged = AtomicBoolean(false) + val acknowledge = { + if (acknowledged.compareAndSet(false, true)) { + runCatching { publish.acknowledge() } + } + } + val pending = AtomicInteger(1) + val settle = { + if (pending.decrementAndGet() == 0) { + acknowledge() + } + } + try { + deliverMessage(context, publish.toPushMessage()) { + pending.incrementAndGet() + val once = AtomicBoolean(false) + val ack: () -> Unit = { + if (once.compareAndSet(false, true)) { + settle() + } + } + ack + } + } finally { + settle() + } + if (!acknowledged.get()) { + worker.schedule({ acknowledge() }, ACK_TIMEOUT_SECONDS, TimeUnit.SECONDS) + } + } + + // Deliver a message to the matching live callbacks; with none, to the declared PushReceiver + // and/or a notification per saved subscription that matches it. [deferAck] hands a listener + // that acknowledges messages itself its acknowledgement. + private fun deliverMessage(context: Context, message: PushMessage, deferAck: () -> () -> Unit) { + val matching = listeners.filter { listener -> listener.topics.any { matchesTopic(it, message.topic) } } + // Saved background subscriptions no live one has taken over; they deliver even when a live + // foreground-only subscription on an overlapping filter received the message too. + val entries = synchronized(lock) { saved }.filter { matchesTopic(it.filter, message.topic) } + if (matching.isEmpty() && entries.isEmpty()) { + return + } + for (listener in matching) { + try { + val acknowledging = listener.acknowledgingCallback + if (acknowledging != null) { + acknowledging(message, deferAck()) + } else { + listener.callback(message) + } + } catch (e: Exception) { + report(e) + } + } + val wakeLock = acquireWakeLock(context) + try { + // The app's PushReceiver gets only messages no live callback received. + val handled = matching.isEmpty() && deliverToReceivers(context, message) + val titles = matching.filter { it.background }.map { it.title ?: message.topic } + + if (handled) emptyList() else entries.map { it.title ?: message.topic } + titles.distinct().forEach { notify(context, message, it) } + } finally { + releaseWakeLock(wakeLock) + } + } + + // Call every PushReceiver the app declares. Returns whether one handled the message. + private fun deliverToReceivers(context: Context, message: PushMessage): Boolean { + val intent = Intent(ACTION_MESSAGE).setPackage(context.packageName) + val receivers = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.queryBroadcastReceivers(intent, PackageManager.ResolveInfoFlags.of(0)) + } else { + @Suppress("DEPRECATION") + context.packageManager.queryBroadcastReceivers(intent, 0) + } + var handled = false + for (info in receivers) { + val name = info.activityInfo?.name ?: continue + try { + val receiver = Class.forName(name, true, context.classLoader).getDeclaredConstructor().newInstance() + if (receiver is PushReceiver && receiver.onMessage(context, message)) { + handled = true + } + } catch (e: Exception) { + report(e) + } + } + return handled + } + + /** Post a notification for [message] that opens the app, with the message in its extras. */ + fun notify(context: Context, message: PushMessage, title: String) { + val manager = NotificationManagerCompat.from(context) + if (!manager.areNotificationsEnabled()) { + return + } + createChannels(context) + val id = 31 * message.hashCode() + title.hashCode() + val builder = NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle(title) + .setContentText(message.data) + .setSmallIcon(notificationIcon(context)) + .setAutoCancel(true) + context.packageManager.getLaunchIntentForPackage(context.packageName)?.let { launch -> + launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + .putExtra(EXTRA_TOPIC, message.topic) + .putExtra(EXTRA_PAYLOAD, message.data) + builder.setContentIntent( + PendingIntent.getActivity(context, id, launch, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE), + ) + } + try { + manager.notify(id, builder.build()) + } catch (e: SecurityException) { + // POST_NOTIFICATIONS not granted; nothing to show. + } + } + + /** The ongoing notification the foreground service shows, on its own quiet channel. */ + fun ongoingNotification(context: Context) = NotificationCompat.Builder(context, SERVICE_CHANNEL_ID) + .also { createChannels(context) } + .setContentTitle(ongoingTitle) + .setContentText("Listening for messages") + .setSmallIcon(notificationIcon(context)) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .build() + + private fun createChannels(context: Context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return + } + val manager = context.getSystemService(NotificationManager::class.java) ?: return + manager.createNotificationChannel(NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_HIGH)) + manager.createNotificationChannel( + NotificationChannel(SERVICE_CHANNEL_ID, "Background connection", NotificationManager.IMPORTANCE_LOW), + ) + } + + // The icon named by the app's ``, else a + // generic one. + private fun notificationIcon(context: Context): Int { + val metaData = runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.getApplicationInfo(context.packageName, PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong())) + } else { + @Suppress("DEPRECATION") + context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA) + }.metaData + }.getOrNull() + return metaData?.getInt(NOTIFICATION_ICON, 0)?.takeIf { it != 0 } ?: android.R.drawable.stat_notify_chat + } + + private fun ensureService(context: Context) { + if (!wantsService(context)) { + if (serviceRunning) { + context.stopService(Intent(context, PushService::class.java)) + } + return + } + if (serviceRunning) { + return + } + try { + ContextCompat.startForegroundService(context, Intent(context, PushService::class.java)) + } catch (e: Exception) { + // Android 12+ refuses a foreground-service start from the background; the scheduled + // runs keep delivering, and the next start from the foreground succeeds. + if (!serviceStartReported) { + serviceStartReported = true + report(e) + } + } + } + + // Arm the next run in [delayMs]: the job, and an alarm just after it in case the job is late. + private fun schedule(context: Context, delayMs: Long, jobToo: Boolean = true) { + val interval = if (delayMs == INTERVAL_MS && privileged(context)) PRIVILEGED_INTERVAL_MS else delayMs + if (jobToo) { + try { + val job = JobInfo.Builder(JOB_ID, ComponentName(context, PushJobService::class.java)) + .setMinimumLatency(interval) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setPersisted(true) + .build() + context.getSystemService(JobScheduler::class.java)?.schedule(job) + } catch (e: Exception) { + report(e) + } + } + val alarms = context.getSystemService(AlarmManager::class.java) ?: return + val at = SystemClock.elapsedRealtime() + interval + 1_000L + val intent = tickIntent(context) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarms.canScheduleExactAlarms()) { + alarms.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, at, intent) + } else { + alarms.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, at, intent) + } + } catch (e: SecurityException) { + alarms.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, at, intent) + } + } + + private fun cancelSchedule(context: Context) { + context.getSystemService(JobScheduler::class.java)?.cancel(JOB_ID) + context.getSystemService(AlarmManager::class.java)?.cancel(tickIntent(context)) + } + + private fun tickIntent(context: Context): PendingIntent = PendingIntent.getBroadcast( + context, + 0, + Intent(context, PushAlarmReceiver::class.java).setAction(ACTION_TICK), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + + // Allowed exact alarms or exempt from battery optimisation: the alarm is reliable, so the + // runs can be spaced further apart. + private fun privileged(context: Context): Boolean = + Build.VERSION.SDK_INT < Build.VERSION_CODES.S || + context.getSystemService(AlarmManager::class.java)?.canScheduleExactAlarms() == true || + context.getSystemService(PowerManager::class.java)?.isIgnoringBatteryOptimizations(context.packageName) == true + + private fun activeNetwork(context: Context): Network? = + runCatching { context.getSystemService(ConnectivityManager::class.java)?.activeNetwork }.getOrNull() + + private fun networkChanged(context: Context): Boolean { + val now = activeNetwork(context) + return now != null && connectedNetwork != null && now != connectedNetwork + } + + // Reconnect as soon as the default network changes (e.g. from cellular to Wi-Fi) while the + // process is alive, instead of waiting for the next run to notice. + private fun registerNetworkCallback(context: Context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return + } + synchronized(lock) { + if (networkCallback != null) { + return + } + val callback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + worker.execute { + if (mqtt != null && network != connectedNetwork) { + converge(context, heartbeat = false) + } + } + } + } + try { + context.getSystemService(ConnectivityManager::class.java)?.registerDefaultNetworkCallback(callback) + networkCallback = callback + } catch (e: Exception) { + report(e) + } + } + } + + private fun unregisterNetworkCallback(context: Context) { + val callback = synchronized(lock) { networkCallback.also { networkCallback = null } } ?: return + runCatching { context.getSystemService(ConnectivityManager::class.java)?.unregisterNetworkCallback(callback) } + } + + private fun acquireWakeLock(context: Context): PowerManager.WakeLock? = runCatching { + context.getSystemService(PowerManager::class.java) + ?.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Appwrite:push") + ?.apply { + setReferenceCounted(false) + acquire(WAKE_LOCK_MS) + } + }.getOrNull() + + private fun releaseWakeLock(wakeLock: PowerManager.WakeLock?) { + runCatching { + if (wakeLock?.isHeld == true) { + wakeLock.release() + } + } + } +} + +/** + * Background push state in the app's no-backup directory, so a restored backup on another + * device never carries this install's id or credential: the saved subscriptions with their + * connection config (cleared when background delivery stops), and the settings that outlive + * it (the install id, the foreground-mode choice, a refusal waiting for onError). + */ +internal object PushStore { + private const val STATE_FILE = "appwrite_push_state.json" + private const val SETTINGS_FILE = "appwrite_push_settings.json" + + fun loadState(context: Context): Pair>? = synchronized(this) { + val json = read(context, STATE_FILE) ?: return null + runCatching { + val config = json.getJSONObject("config").let { + PushConfig( + host = it.getString("host"), + port = it.getInt("port"), + tls = it.getBoolean("tls"), + tlsInsecure = it.getBoolean("tlsInsecure"), + clientId = it.getString("clientId"), + keepAlive = it.getInt("keepAlive"), + authMethod = it.getString("authMethod"), + credential = it.getString("credential"), + project = it.getString("project"), + ) + } + val list = json.getJSONArray("entries") + val entries = (0 until list.length()).map { index -> + val entry = list.getJSONObject(index) + PushEntry( + filter = entry.getString("filter"), + retry = entry.getBoolean("retry"), + title = if (entry.isNull("title")) null else entry.getString("title"), + ) + } + config to entries + }.getOrNull() + } + + fun saveState(context: Context, config: PushConfig, entries: List) = synchronized(this) { + val json = JSONObject() + .put( + "config", + JSONObject() + .put("host", config.host) + .put("port", config.port) + .put("tls", config.tls) + .put("tlsInsecure", config.tlsInsecure) + .put("clientId", config.clientId) + .put("keepAlive", config.keepAlive) + .put("authMethod", config.authMethod) + .put("credential", config.credential) + .put("project", config.project), + ) + .put( + "entries", + JSONArray(entries.map { JSONObject().put("filter", it.filter).put("retry", it.retry).put("title", it.title ?: JSONObject.NULL) }), + ) + write(context, STATE_FILE, json) + } + + fun clearState(context: Context) = synchronized(this) { + file(context, STATE_FILE).delete() + } + + /** A random id for this install, created once, which keeps the broker's replay position. */ + fun installId(context: Context): String = synchronized(this) { + val settings = read(context, SETTINGS_FILE) ?: JSONObject() + settings.optString("installId").ifEmpty { + val id = UUID.randomUUID().toString() + write(context, SETTINGS_FILE, settings.put("installId", id)) + id + } + } + + fun foreground(context: Context): Boolean = synchronized(this) { + read(context, SETTINGS_FILE)?.optBoolean("foreground", false) ?: false + } + + fun setForeground(context: Context, enabled: Boolean) = synchronized(this) { + write(context, SETTINGS_FILE, (read(context, SETTINGS_FILE) ?: JSONObject()).put("foreground", enabled)) + } + + /** The saved adaptive heartbeat; the default interval until one was saved. */ + fun heartbeat(context: Context): PushHeartbeat = synchronized(this) { + val json = read(context, SETTINGS_FILE)?.optJSONObject("heartbeat") ?: return PushHeartbeat(PushBackground.HEARTBEAT_MS) + PushHeartbeat( + intervalMs = json.optLong("intervalMs", PushBackground.HEARTBEAT_MS), + failures = json.optInt("failures", 0), + shrunkAt = json.optLong("shrunkAt", 0L), + ) + } + + fun setHeartbeat(context: Context, state: PushHeartbeat) = synchronized(this) { + val heartbeat = JSONObject() + .put("intervalMs", state.intervalMs) + .put("failures", state.failures) + .put("shrunkAt", state.shrunkAt) + write(context, SETTINGS_FILE, (read(context, SETTINGS_FILE) ?: JSONObject()).put("heartbeat", heartbeat)) + } + + fun setStoppedError(context: Context, message: String) = synchronized(this) { + write(context, SETTINGS_FILE, (read(context, SETTINGS_FILE) ?: JSONObject()).put("stoppedError", message)) + } + + /** The refusal that stopped background delivery while no onError was registered, once. */ + fun takeStoppedError(context: Context): String? = synchronized(this) { + val settings = read(context, SETTINGS_FILE) ?: return null + val message = settings.optString("stoppedError").ifEmpty { return null } + settings.remove("stoppedError") + write(context, SETTINGS_FILE, settings) + message + } + + private fun file(context: Context, name: String) = AtomicFile(File(context.noBackupFilesDir, name)) + + private fun read(context: Context, name: String): JSONObject? = + runCatching { JSONObject(String(file(context, name).readFully(), Charsets.UTF_8)) }.getOrNull() + + private fun write(context: Context, name: String, json: JSONObject) { + val file = file(context, name) + val out = try { + file.startWrite() + } catch (e: IOException) { + Log.e(LOG_TAG, "Could not save push state", e) + return + } + try { + out.write(json.toString().toByteArray(Charsets.UTF_8)) + file.finishWrite(out) + } catch (e: IOException) { + file.failWrite(out) + Log.e(LOG_TAG, "Could not save push state", e) + } + } +} diff --git a/library/src/main/java/io/appwrite/services/PushCore.kt b/library/src/main/java/io/appwrite/services/PushCore.kt new file mode 100644 index 00000000..20c56242 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushCore.kt @@ -0,0 +1,374 @@ +package io.appwrite.services + +import android.content.Context +import android.os.Build +import android.util.Base64 +import com.hivemq.client.mqtt.MqttClient +import com.hivemq.client.mqtt.datatypes.MqttUtf8String +import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedContext +import com.hivemq.client.mqtt.lifecycle.MqttDisconnectSource +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.Mqtt5ClientConfig +import com.hivemq.client.mqtt.mqtt5.auth.Mqtt5EnhancedAuthMechanism +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5DisconnectException +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5Auth +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5AuthBuilder +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5EnhancedAuthBuilder +import com.hivemq.client.mqtt.mqtt5.message.connect.Mqtt5Connect +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import com.hivemq.client.mqtt.mqtt5.message.disconnect.Mqtt5Disconnect +import com.hivemq.client.mqtt.mqtt5.message.disconnect.Mqtt5DisconnectReasonCode +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish +import io.appwrite.exceptions.AppwriteException +import org.json.JSONObject +import java.security.KeyStore +import java.security.Provider +import java.security.cert.X509Certificate +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import javax.net.ssl.ManagerFactoryParameters +import javax.net.ssl.TrustManager +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.TrustManagerFactorySpi +import javax.net.ssl.X509TrustManager + +/** + * A message delivered on a subscribed topic — the MQTT push analog of a realtime event. + */ +data class PushMessage( + val topic: String, + val payload: ByteArray, + val qos: Int, +) { + /** The payload decoded as UTF-8 text, as it was sent. */ + val data: String + get() = String(payload, Charsets.UTF_8) +} + +/** Connection parameters shared between the in-process client and the foreground service. */ +internal data class PushConfig( + val host: String, + val port: Int, + val tls: Boolean, + val tlsInsecure: Boolean, + val clientId: String, + val keepAlive: Int, + val authMethod: String, + val credential: String, + val project: String, +) + +/** + * The userId claim from an Appwrite JWT, used as the default MQTT client id so the + * broker's replay cursor is stable per user. Returns "" when the token is not a decodable JWT + * (e.g. a session secret), so the caller can fall back to the raw credential. + */ +internal fun userIdFromJwt(jwt: String): String { + val parts = jwt.split('.') + if (parts.size < 2) { + return "" + } + return runCatching { + val json = String( + Base64.decode(parts[1], Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING), + Charsets.UTF_8, + ) + JSONObject(json).optString("userId", "") + }.getOrDefault("") +} + +/** The user `id` from an Appwrite session secret, or "" when it is not decodable. */ +internal fun userIdFromSession(session: String): String = runCatching { + val json = String(Base64.decode(session, Base64.DEFAULT), Charsets.UTF_8) + JSONObject(json).optString("id", "") +}.getOrDefault("") + +/** + * The default MQTT client id: one per user and install (`-`, or the install id + * alone when the credential names no user). The broker keeps the replay position per user and + * client id, so each device resumes its own backlog across restarts, and two devices of one user + * do not take over each other's session. + */ +internal fun defaultPushClientId(context: Context, authMethod: String, credential: String): String { + val userId = if (authMethod == "appwrite-session") userIdFromSession(credential) else userIdFromJwt(credential) + val installId = PushStore.installId(context) + return if (userId.isEmpty()) installId else "$userId-$installId" +} + +/** + * Build (but do not connect) an MQTT 5 client from [config]. [onError] receives every connection + * error (a lost connection, a failed automatic reconnect, a broker DISCONNECT) except a refused + * initial CONNECT, which [pushConnect] throws instead. + */ +internal fun buildPushClient( + config: PushConfig, + onConnected: (() -> Unit)? = null, + onDisconnected: (() -> Unit)? = null, + onError: ((Throwable) -> Unit)? = null, + onAuthRefused: ((Throwable) -> Unit)? = null, +): Mqtt5AsyncClient { + // config.clientId is stable per user (see buildConfig), so a reconnect rebuild reuses it + // and resumes the same session. + var builder = MqttClient.builder() + .useMqttVersion5() + .serverHost(config.host) + .serverPort(config.port) + // Recover from an unexpected disconnect (network drop, broker restart) with HiveMQ's + // exponential backoff. A user-initiated disconnect() (see close()) does not trigger it. + // Because clean start is off and the broker resumes the same session, subscriptions and + // the QoS-1 replay cursor persist, so delivery continues without resubscribing (which + // would stack a second per-subscription callback). + .automaticReconnectWithDefaultConfig() + + if (config.clientId.isNotEmpty()) { + builder = builder.identifier(config.clientId) + } + + // Whether this client has connected yet: until it has, a refused CONNECT is final. + val connected = AtomicBoolean(false) + builder = builder.addConnectedListener { + connected.set(true) + onConnected?.invoke() + } + builder = builder.addDisconnectedListener { context -> + // A refused first CONNECT (bad credential, rate limit) is final: stop the automatic + // reconnect so pushConnect fails with the broker's reason instead of retrying forever. + val refused = !connected.get() && context.cause is Mqtt5ConnAckException + // With [onAuthRefused], a credential refused later (a failed automatic reconnect or + // re-auth) is final too, instead of being retried with the same credential forever. + val authRefused = onAuthRefused != null && connected.get() && + context.source != MqttDisconnectSource.USER && isAuthRefusal(context.cause) + if (refused || authRefused) { + context.reconnector.reconnect(false) + } + onDisconnected?.invoke() + if (!refused) { + val error = pushDisconnectError(context) + if (authRefused && error != null) { + // Reported by onAuthRefused, which also stops for good. + onAuthRefused?.invoke(error) + } else { + error?.let { onError?.invoke(it) } + } + } + } + + if (config.tls) { + builder = if (config.tlsInsecure) { + builder.sslConfig() + .trustManagerFactory(PushInsecureTrustManagerFactory) + .hostnameVerifier { _, _ -> true } + .applySslConfig() + } else { + builder.sslWithDefaultConfig() + } + } + + return builder.buildAsync() +} + +/** + * Connect with Appwrite enhanced auth; blocks until CONNACK. Throws an + * [AppwriteException] on failure, whose message is the broker's MQTT 5 + * Reason String when it refused the CONNECT with one. + */ +internal fun Mqtt5AsyncClient.pushConnect(config: PushConfig) { + try { + sendPushConnect(config) + } catch (e: Exception) { + throw connectError((e as? ExecutionException)?.cause ?: e) + } +} + +/** The error a failed CONNECT reports: the broker's Reason String when it refused one. */ +internal fun connectError(cause: Throwable): AppwriteException = + if (cause is Mqtt5ConnAckException) { + connAckError(cause) + } else { + AppwriteException(cause.message ?: "MQTT connection failed") + } + +/** + * Whether [error] is the broker refusing the credential (a CONNECT or re-auth), which retrying + * with the same credential cannot fix, unlike a rate limit or a network failure. + */ +internal fun isAuthRefusal(error: Throwable?): Boolean = when (error) { + is Mqtt5ConnAckException -> error.mqttMessage.reasonCode in setOf( + Mqtt5ConnAckReasonCode.NOT_AUTHORIZED, + Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD, + Mqtt5ConnAckReasonCode.BAD_AUTHENTICATION_METHOD, + Mqtt5ConnAckReasonCode.BANNED, + ) + is Mqtt5DisconnectException -> error.mqttMessage.reasonCode == Mqtt5DisconnectReasonCode.NOT_AUTHORIZED + else -> false +} + +/** Send the CONNECT and wait for the CONNACK, at most [timeoutSeconds] when given. */ +internal fun Mqtt5AsyncClient.sendPushConnect(config: PushConfig, timeoutSeconds: Long? = null) { + val connAck = connectWith() + .enhancedAuth(PushAuthMechanism(config.authMethod, config.credential)) + // Clean start is always off, so the broker keeps this client's session and can + // redeliver missed messages to QoS-1 subscriptions on reconnect. + .cleanStart(false) + .keepAlive(config.keepAlive) + .userProperties() + .add("projectId", config.project) + .applyUserProperties() + .send() + if (timeoutSeconds == null) { + connAck.get() + } else { + connAck.get(timeoutSeconds, TimeUnit.SECONDS) + } +} + +/** + * The error a lost connection reports, or null when it is not an error (a disconnect the app + * asked for, or a broker DISCONNECT with a success reason code). The broker explains a refused + * CONNECT or a DISCONNECT it sends with an MQTT 5 Reason String, which becomes the message. + */ +internal fun pushDisconnectError(context: MqttClientDisconnectedContext): Throwable? { + val cause = context.cause + return when { + context.source == MqttDisconnectSource.USER -> null + cause is Mqtt5ConnAckException -> connAckError(cause) + cause is Mqtt5DisconnectException && context.source == MqttDisconnectSource.SERVER -> { + val disconnect = cause.mqttMessage + if (disconnect.reasonCode.isError) { + AppwriteException( + disconnect.reasonString.map { it.toString() } + .orElse("Disconnected by the broker (reason ${disconnect.reasonCode})"), + ) + } else { + null + } + } + else -> cause + } +} + +/** A refused CONNECT, with the CONNACK Reason String as its message when the broker sent one. */ +internal fun connAckError(e: Mqtt5ConnAckException): AppwriteException = + AppwriteException( + e.mqttMessage.reasonString.map { it.toString() }.orElse(e.message ?: "MQTT connection refused"), + ) + +internal fun Mqtt5Publish.toPushMessage(): PushMessage = + PushMessage( + topic = topic.toString(), + payload = payloadAsBytes, + qos = qos.code, + ) + +/** + * Single-step MQTT 5 enhanced auth: the method and credential are placed in the CONNECT + * packet and the broker accepts them in the CONNACK, with no AUTH round-trip. + */ +internal class PushAuthMechanism( + private val method: String, + private val credential: String, +) : Mqtt5EnhancedAuthMechanism { + + override fun getMethod(): MqttUtf8String = MqttUtf8String.of(method) + + override fun getTimeout(): Int = 30 + + override fun onAuth( + clientConfig: Mqtt5ClientConfig, + connect: Mqtt5Connect, + authBuilder: Mqtt5EnhancedAuthBuilder, + ): CompletableFuture { + authBuilder.data(credential.toByteArray(Charsets.UTF_8)) + return CompletableFuture.completedFuture(null) + } + + override fun onReAuth( + clientConfig: Mqtt5ClientConfig, + authBuilder: Mqtt5AuthBuilder, + ): CompletableFuture { + authBuilder.data(credential.toByteArray(Charsets.UTF_8)) + return CompletableFuture.completedFuture(null) + } + + override fun onContinue( + clientConfig: Mqtt5ClientConfig, + auth: Mqtt5Auth, + authBuilder: Mqtt5AuthBuilder, + ): CompletableFuture = CompletableFuture.completedFuture(true) + + override fun onAuthSuccess( + clientConfig: Mqtt5ClientConfig, + connAck: Mqtt5ConnAck, + ): CompletableFuture = CompletableFuture.completedFuture(true) + + override fun onReAuthSuccess( + clientConfig: Mqtt5ClientConfig, + auth: Mqtt5Auth, + ): CompletableFuture = CompletableFuture.completedFuture(true) + + override fun onAuthRejected(clientConfig: Mqtt5ClientConfig, connAck: Mqtt5ConnAck) = Unit + + override fun onReAuthRejected(clientConfig: Mqtt5ClientConfig, disconnect: Mqtt5Disconnect) = Unit + + override fun onAuthError(clientConfig: Mqtt5ClientConfig, cause: Throwable) = Unit + + override fun onReAuthError(clientConfig: Mqtt5ClientConfig, cause: Throwable) = Unit +} + +/** + * A [TrustManagerFactory] that accepts any certificate — used when `tlsInsecure` is set + * (for a broker whose certificate does not chain to a public root). + */ +internal object PushInsecureTrustManagerFactory : TrustManagerFactory( + object : TrustManagerFactorySpi() { + override fun engineInit(keyStore: KeyStore?) = Unit + + override fun engineInit(parameters: ManagerFactoryParameters?) = Unit + + override fun engineGetTrustManagers(): Array = arrayOf( + object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) = Unit + + override fun checkServerTrusted(chain: Array?, authType: String?) = Unit + + override fun getAcceptedIssuers(): Array = emptyArray() + }, + ) + }, + @Suppress("DEPRECATION") object : Provider("AppwriteInsecure", 1.0, "insecure trust-all") {}, + "AppwriteInsecure", +) + +/** A live in-app subscription hosted in the background: its topics, callback, notification opt-in and QoS. */ +internal class PushListener( + val topics: Set, + val callback: (PushMessage) -> Unit, + val background: Boolean, + val title: String?, + val retry: Boolean, + // Called instead of [callback] when set, with the acknowledgement to run once the message was + // handled: the React Native and Flutter bridges acknowledge after their callback has run. + val acknowledgingCallback: ((PushMessage, () -> Unit) -> Unit)? = null, +) + +/** MQTT topic-filter match with '+' (single level) and '#' (multi level). */ +internal fun matchesTopic(filter: String, topic: String): Boolean { + val filterParts = filter.split('/') + val topicParts = topic.split('/') + for (i in filterParts.indices) { + if (filterParts[i] == "#") { + return true + } + if (i >= topicParts.size) { + return false + } + if (filterParts[i] != "+" && filterParts[i] != topicParts[i]) { + return false + } + } + return filterParts.size == topicParts.size +} diff --git a/library/src/main/java/io/appwrite/services/PushReceiver.kt b/library/src/main/java/io/appwrite/services/PushReceiver.kt new file mode 100644 index 00000000..92d04727 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushReceiver.kt @@ -0,0 +1,33 @@ +package io.appwrite.services + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +/** + * Handles background push messages in code when no in-app callback is listening, e.g. after + * the process was started in the background to reconnect. Subclass it and declare it in your + * manifest with the push message action: + * + * ```xml + * + * + * + * + * + * ``` + * + * The SDK calls [onMessage] directly on a background thread, under a wakelock of about ten + * seconds, so hand longer work to WorkManager. Messages a live callback receives do not + * reach it. + */ +abstract class PushReceiver : BroadcastReceiver() { + /** + * Handle [message]. Return true when you handled it yourself, so the SDK does not post its + * default notification; return false to have it posted as well. + */ + abstract fun onMessage(context: Context, message: PushMessage): Boolean + + // Messages arrive through onMessage, never as a broadcast. + final override fun onReceive(context: Context, intent: Intent) = Unit +} diff --git a/library/src/main/java/io/appwrite/services/PushService.kt b/library/src/main/java/io/appwrite/services/PushService.kt new file mode 100644 index 00000000..e2a05f89 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushService.kt @@ -0,0 +1,67 @@ +package io.appwrite.services + +import android.app.Service +import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Build +import android.os.IBinder +import androidx.core.app.ServiceCompat + +/** + * The foreground mode of background push delivery ([Push.setForeground]): a foreground service + * that keeps the process at foreground priority, so the connection held by the process stays + * up and delivers immediately, also during Doze. The system restarts it after a kill, and it + * restores the saved subscriptions. Started by the SDK; not meant to be started directly. + */ +class PushService : Service() { + override fun onBind(intent: Intent?): IBinder? = null + + override fun onCreate() { + super.onCreate() + PushBackground.serviceRunning = true + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (!PushBackground.wantsService(this)) { + stopSelf() + return START_NOT_STICKY + } + try { + startForegroundCompat() + } catch (e: Exception) { + // E.g. a background start refused on Android 12+; the scheduled runs keep delivering. + stopSelf() + PushBackground.report(e) + return START_NOT_STICKY + } + PushBackground.tick(this) {} + // Recreated after a kill with a null intent; the saved subscriptions are restored then. + return START_STICKY + } + + private fun startForegroundCompat() { + val notification = PushBackground.ongoingNotification(this) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ServiceCompat.startForeground( + this, + PushBackground.NOTIFICATION_ID, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING, + ) + } else { + startForeground(PushBackground.NOTIFICATION_ID, notification) + } + } + + // Some devices kill the process shortly after its task is swiped away; a run a few seconds + // later restarts the service. + override fun onTaskRemoved(rootIntent: Intent?) { + PushBackground.scheduleRestart(this) + super.onTaskRemoved(rootIntent) + } + + override fun onDestroy() { + PushBackground.serviceRunning = false + super.onDestroy() + } +} diff --git a/library/src/main/java/io/appwrite/services/PushWakeups.kt b/library/src/main/java/io/appwrite/services/PushWakeups.kt new file mode 100644 index 00000000..091fd528 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/PushWakeups.kt @@ -0,0 +1,46 @@ +package io.appwrite.services + +import android.app.job.JobParameters +import android.app.job.JobService +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +/** + * The scheduled run of background push delivery: reconnects and re-arms itself. Scheduled + * by the SDK; not meant to be started directly. + */ +class PushJobService : JobService() { + override fun onStartJob(params: JobParameters): Boolean { + PushBackground.tick(this) { jobFinished(params, false) } + return true + } + + // The run re-arms the next one itself, so a stopped run needs no retry. + override fun onStopJob(params: JobParameters): Boolean = false +} + +/** + * The alarm that backs up the scheduled job when it runs late. Scheduled by the SDK; not meant + * to be sent directly. + */ +class PushAlarmReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != PushBackground.ACTION_TICK) { + return + } + val pending = goAsync() + PushBackground.tick(context) { pending.finish() } + } +} + +/** Resumes background push delivery after a reboot or an app update. */ +class PushBootReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_BOOT_COMPLETED && intent.action != Intent.ACTION_MY_PACKAGE_REPLACED) { + return + } + val pending = goAsync() + PushBackground.tick(context) { pending.finish() } + } +} diff --git a/library/src/main/java/io/appwrite/services/Realtime.kt b/library/src/main/java/io/appwrite/services/Realtime.kt index 01987db8..dd2720f3 100644 --- a/library/src/main/java/io/appwrite/services/Realtime.kt +++ b/library/src/main/java/io/appwrite/services/Realtime.kt @@ -432,7 +432,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { launch(IO) { if (isStale(webSocket)) return@launch try { - val message = text.fromJson() + val message = RealtimeResponse.from(text.fromJson>()) when (message.type) { TYPE_ERROR -> handleResponseError(message) TYPE_CONNECTED -> handleResponseConnected(message) @@ -458,13 +458,19 @@ class Realtime(client: Client) : Service(client), CoroutineScope { } private fun handleResponseError(message: RealtimeResponse) { - val ex = message.data?.jsonCast() ?: RuntimeException("Data is not present") - throw ex + val data = message.data as? Map<*, *> ?: throw RuntimeException("Data is not present") + throw AppwriteException( + message = data["message"] as? String, + code = (data["code"] as? Number)?.toInt(), + type = data["type"] as? String, + response = data["response"] as? String, + ) } private suspend fun handleResponseEvent(message: RealtimeResponse) { - val messageData = message.data?.jsonCast>() ?: return - val event = messageData.jsonCast>() + @Suppress("UNCHECKED_CAST") + val messageData = message.data as? Map ?: return + val event = RealtimeResponseEvent.from(messageData) ?: return if (event.channels.isEmpty()) { return diff --git a/library/src/main/java/io/appwrite/services/Storage.kt b/library/src/main/java/io/appwrite/services/Storage.kt index cc4f8608..6fe1f966 100644 --- a/library/src/main/java/io/appwrite/services/Storage.kt +++ b/library/src/main/java/io/appwrite/services/Storage.kt @@ -12,7 +12,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import java.io.File /** - * The Storage service allows you to manage your project files. */ + * The Storage service allows you to manage your project files. + */ class Storage(client: Client) : Service(client) { /** * Get a list of all the user files. You can use the query params to filter your results. @@ -114,9 +115,9 @@ class Storage(client: Client) : Service(client) { apiParams, responseType = io.appwrite.models.File::class.java, converter, - paramName, - idParamName, - onProgress, + paramName = paramName, + idParamName = idParamName, + onProgress = onProgress, ) } diff --git a/library/src/main/java/io/appwrite/services/TablesDB.kt b/library/src/main/java/io/appwrite/services/TablesDB.kt index 7a8881df..34217c41 100644 --- a/library/src/main/java/io/appwrite/services/TablesDB.kt +++ b/library/src/main/java/io/appwrite/services/TablesDB.kt @@ -10,21 +10,25 @@ import okhttp3.Cookie import java.io.File /** - * The TablesDB service allows you to create structured tables of columns, query and filter lists of rows */ + * The TablesDB service allows you to create structured tables of columns, query and filter lists of rows + */ class TablesDB(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/tablesdb/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/main/java/io/appwrite/services/Teams.kt b/library/src/main/java/io/appwrite/services/Teams.kt index 7708fcca..7227eba6 100644 --- a/library/src/main/java/io/appwrite/services/Teams.kt +++ b/library/src/main/java/io/appwrite/services/Teams.kt @@ -10,7 +10,8 @@ import okhttp3.Cookie import java.io.File /** - * The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources */ + * The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources + */ class Teams(client: Client) : Service(client) { /** * Get a list of all the teams in which the current user is a member. You can use the parameters to filter your results. @@ -77,7 +78,7 @@ class Teams(client: Client) : Service(client) { * * @param teamId Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param name Team name. Max length: 128 chars. - * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long. + * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long. * @return [io.appwrite.models.Team] */ @JvmOverloads @@ -117,7 +118,7 @@ class Teams(client: Client) : Service(client) { * * @param teamId Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. * @param name Team name. Max length: 128 chars. - * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long. + * @param roles Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long. * @return [io.appwrite.models.Team] */ @JvmOverloads diff --git a/library/src/main/java/io/appwrite/services/VectorsDB.kt b/library/src/main/java/io/appwrite/services/VectorsDB.kt index 9fa2f291..9ad46da2 100644 --- a/library/src/main/java/io/appwrite/services/VectorsDB.kt +++ b/library/src/main/java/io/appwrite/services/VectorsDB.kt @@ -10,21 +10,25 @@ import okhttp3.Cookie import java.io.File /** - * */ + * + */ class VectorsDB(client: Client) : Service(client) { /** * List transactions across all databases. * * @param queries Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). + * @param total When set to false, the total count returned will be 0 and will not be calculated. * @return [io.appwrite.models.TransactionList] */ @JvmOverloads suspend fun listTransactions( queries: List? = null, + total: Boolean? = null, ): io.appwrite.models.TransactionList { val apiPath = "/vectorsdb/transactions" val apiParams = mutableMapOf( "queries" to queries, + "total" to total, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/test/java/io/appwrite/ClientJavaInteropTest.java b/library/src/test/java/io/appwrite/ClientJavaInteropTest.java index 3019bb94..ea50bcff 100644 --- a/library/src/test/java/io/appwrite/ClientJavaInteropTest.java +++ b/library/src/test/java/io/appwrite/ClientJavaInteropTest.java @@ -22,4 +22,13 @@ public void endpointSettersAreFluentFromJava() { assertEquals("https://cloud.appwrite.io/v1", client.getEndpoint()); assertEquals("wss://cloud.appwrite.io/v1", client.getEndpointRealtime()); } + + @Test + public void positionalSelfSignedFollowsTheRealtimeEndpoint() { + Context context = ApplicationProvider.getApplicationContext(); + Client client = new Client(context, "https://cloud.appwrite.io/v1", "wss://cloud.appwrite.io/v1", true); + + assertEquals("https://cloud.appwrite.io/v1", client.getEndpoint()); + assertEquals("wss://cloud.appwrite.io/v1", client.getEndpointRealtime()); + } } diff --git a/library/src/test/java/io/appwrite/TopicTest.kt b/library/src/test/java/io/appwrite/TopicTest.kt new file mode 100644 index 00000000..0dd65bf0 --- /dev/null +++ b/library/src/test/java/io/appwrite/TopicTest.kt @@ -0,0 +1,53 @@ +package io.appwrite + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Test + +class TopicTest { + private fun assertPathFails(levels: List) { + assertThrows(IllegalArgumentException::class.java) { Topic.path(levels) } + assertThrows(IllegalArgumentException::class.java) { Topic.any().path(levels) } + } + + @Test + fun pathJoinsLevels() { + assertEquals("user/123/notification", Topic.path(listOf("user", "123", "notification")).toString()) + } + + @Test + fun pathAppendsLevels() { + assertEquals( + "org/42/user/123/notification", + Topic.path(listOf("org", "42", "user", "123")).path(listOf("notification")).toString(), + ) + } + + @Test + fun anyAppendsSingleLevelWildcard() { + assertEquals("user/+/notification", Topic.path(listOf("user")).any().path(listOf("notification")).toString()) + assertEquals("chat/+/+/message", Topic.path(listOf("chat")).any().any().path(listOf("message")).toString()) + } + + @Test + fun allEndsTopic() { + assertEquals("org/+/logs/#", Topic.path(listOf("org")).any().path(listOf("logs")).all().toString()) + } + + @Test + fun staticAnyAndAll() { + assertEquals("+/notification", Topic.any().path(listOf("notification")).toString()) + assertEquals("#", Topic.all().toString()) + } + + @Test + fun pathRejectsInvalidLevels() { + assertPathFails(emptyList()) + assertPathFails(listOf("user", "")) + assertPathFails(listOf("user/123")) + assertPathFails(listOf("+")) + assertPathFails(listOf("user", "a+b")) + assertPathFails(listOf("user", "#")) + assertPathFails(listOf("a#b")) + } +} From b556acc30e8cb0d0cea644160533f8599f6a9511 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 6 Oct 2026 11:23:59 +0000 Subject: [PATCH 12/12] chore: update Android SDK to 29.1.0-rc.0 --- CHANGELOG.md | 7 +++ README.md | 9 ++- library/src/main/java/io/appwrite/Client.kt | 12 ++-- .../main/java/io/appwrite/services/Avatars.kt | 1 + .../io/appwrite/services/PushBackground.kt | 60 +++++++++++++++++-- .../main/java/io/appwrite/services/Storage.kt | 1 + 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef16837..ccff23c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 29.1.0-rc.0 + +* Added: optional `method` param on `Client.chunkedUpload()` to set the upload HTTP method +* Added: background push notifications render the server `notification` title, body, and image +* Updated: `Avatars.updatePhoto()` uploads via `PUT` +* Fixed: an exactly-chunk-sized file uploads in a single request + ## 29.0.0 * Breaking: minimum Android SDK raised from 23 to 24 (Android 7.0) diff --git a/README.md b/README.md index aeffd888..bb53402e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ repositories { Next, add the dependency to your project's `build.gradle(.kts)` file: ```groovy -implementation("io.appwrite:sdk-for-android:29.0.0") +implementation("io.appwrite:sdk-for-android:29.1.0-rc.0") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 29.0.0 + 29.1.0-rc.0 ``` @@ -78,7 +78,10 @@ app every 15 to 60 seconds to reconnect, and the broker replays what was sent in (`client.setSession(...)`), not a short-lived JWT. When no in-app callback is listening, each message is posted as a notification that opens -your launch activity with `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` in its extras. Set its +your launch activity with `Push.EXTRA_TOPIC` and `Push.EXTRA_PAYLOAD` in its extras. It shows the +title, body and image sent with `createPush`, falling back to the subscription's `title` and the +raw payload for other messages. On Android 13 and later it needs the `POST_NOTIFICATIONS` +permission: call `Push.requestNotificationPermission(activity)` before subscribing. Set its icon with `` in your ``. To handle these messages in code, subclass `PushReceiver` and declare it with the `io.appwrite.push.MESSAGE` action (see its docs). diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index c364e835..1715950c 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -101,7 +101,7 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "29.0.0", + "x-sdk-version" to "29.1.0-rc.0", "x-appwrite-response-format" to "2.3.0" ) config = mutableMapOf() @@ -538,6 +538,7 @@ class Client @JvmOverloads constructor( * @param path * @param headers * @param params + * @param method HTTP method the upload endpoint is declared with; resuming by upload ID always uses GET * * @return [T] */ @@ -551,10 +552,11 @@ class Client @JvmOverloads constructor( paramName: String, idParamName: String? = null, onProgress: ((UploadProgress) -> Unit)? = null, + method: String = "POST", ): T { if (params[paramName] == null) { return call( - method = "POST", + method = method, path, headers, params, @@ -573,7 +575,7 @@ class Client @JvmOverloads constructor( else -> throw UnsupportedOperationException() } - if (size < CHUNK_SIZE || responseType == String::class.java) { + if (size <= CHUNK_SIZE || responseType == String::class.java) { val data = when (input.sourceType) { "file", "path" -> File(input.path).asRequestBody() "bytes" -> (input.data as ByteArray).toRequestBody(input.mimeType.toMediaType()) @@ -585,7 +587,7 @@ class Client @JvmOverloads constructor( data ) return call( - method = "POST", + method = method, path, headers, params, @@ -656,7 +658,7 @@ class Client @JvmOverloads constructor( ) val chunkResult = call( - method = "POST", + method = method, path, chunkHeaders, chunkParams, diff --git a/library/src/main/java/io/appwrite/services/Avatars.kt b/library/src/main/java/io/appwrite/services/Avatars.kt index 3b11d165..2223223f 100644 --- a/library/src/main/java/io/appwrite/services/Avatars.kt +++ b/library/src/main/java/io/appwrite/services/Avatars.kt @@ -325,6 +325,7 @@ class Avatars(client: Client) : Service(client) { paramName = paramName, idParamName = idParamName, onProgress = onProgress, + method = "PUT", ) } diff --git a/library/src/main/java/io/appwrite/services/PushBackground.kt b/library/src/main/java/io/appwrite/services/PushBackground.kt index 52517344..a550a555 100644 --- a/library/src/main/java/io/appwrite/services/PushBackground.kt +++ b/library/src/main/java/io/appwrite/services/PushBackground.kt @@ -10,6 +10,8 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.ConnectivityManager import android.net.Network import android.os.Build @@ -30,6 +32,8 @@ import org.json.JSONArray import org.json.JSONObject import java.io.File import java.io.IOException +import java.net.HttpURLConnection +import java.net.URL import java.util.UUID import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.ExecutionException @@ -41,6 +45,9 @@ import java.util.concurrent.atomic.AtomicInteger private const val LOG_TAG = "AppwritePush" +/** What a message's `notification` block asks a background notification to show; [present] is false without one. */ +internal data class PushNotificationContent(val present: Boolean, val title: String?, val body: String?, val image: String?) + /** A background subscription saved across restarts: its filter, QoS choice and notification title. */ internal data class PushEntry( val filter: String, @@ -108,6 +115,7 @@ internal object PushBackground { private const val HEARTBEAT_FAILURES = 3 private const val HEARTBEAT_RESET_MS = 3 * 24 * 60 * 60 * 1_000L private const val REQUEST_TIMEOUT_SECONDS = 10L + private const val IMAGE_TIMEOUT_MS = 5_000 private const val CONNECT_TIMEOUT_SECONDS = 20L // How long a message waits for a listener that acknowledges it itself (the React Native and @@ -647,9 +655,11 @@ internal object PushBackground { try { // The app's PushReceiver gets only messages no live callback received. val handled = matching.isEmpty() && deliverToReceivers(context, message) + val content = notificationContent(message) val titles = matching.filter { it.background }.map { it.title ?: message.topic } + if (handled) emptyList() else entries.map { it.title ?: message.topic } - titles.distinct().forEach { notify(context, message, it) } + // A title the server sent replaces every subscription's, so one notification is posted. + titles.map { content.title ?: it }.distinct().forEach { notify(context, message, it, content) } } finally { releaseWakeLock(wakeLock) } @@ -679,19 +689,29 @@ internal object PushBackground { return handled } - /** Post a notification for [message] that opens the app, with the message in its extras. */ - fun notify(context: Context, message: PushMessage, title: String) { + /** + * Post a notification for [message] that opens the app, with the message in its extras. It shows + * the server's title, body and image from [content], falling back to [title] and the raw payload. + */ + fun notify(context: Context, message: PushMessage, title: String, content: PushNotificationContent = notificationContent(message)) { val manager = NotificationManagerCompat.from(context) if (!manager.areNotificationsEnabled()) { return } createChannels(context) val id = 31 * message.hashCode() + title.hashCode() + val body = content.body ?: message.data.takeIf { !content.present } val builder = NotificationCompat.Builder(context, CHANNEL_ID) - .setContentTitle(title) - .setContentText(message.data) + .setContentTitle(content.title ?: title) .setSmallIcon(notificationIcon(context)) .setAutoCancel(true) + if (body != null) { + builder.setContentText(body).setStyle(NotificationCompat.BigTextStyle().bigText(body)) + } + content.image?.let { loadImage(it) }?.let { image -> + builder.setLargeIcon(image) + .setStyle(NotificationCompat.BigPictureStyle().bigPicture(image).bigLargeIcon(null as Bitmap?).setSummaryText(body)) + } context.packageManager.getLaunchIntentForPackage(context.packageName)?.let { launch -> launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) .putExtra(EXTRA_TOPIC, message.topic) @@ -707,6 +727,36 @@ internal object PushBackground { } } + /** The server's `notification` block in [message]: nulls when the payload has none or is not JSON. */ + fun notificationContent(message: PushMessage): PushNotificationContent { + val notification = runCatching { JSONObject(message.data) }.getOrNull()?.optJSONObject("notification") + ?: return PushNotificationContent(false, null, null, null) + fun field(name: String) = (notification.opt(name) as? String)?.takeIf { it.isNotEmpty() } + return PushNotificationContent(true, field("title"), field("body"), field("image")) + } + + // Downloads notification images, so a slow one is abandoned without holding up delivery. + private val imageLoader = Executors.newCachedThreadPool { runnable -> + Thread(runnable, "AppwritePushImage").apply { isDaemon = true } + } + + // Download a notification image, or null when it cannot be fetched and decoded within + // IMAGE_TIMEOUT_MS overall. On timeout the connection is closed, which ends a read in progress. + private fun loadImage(url: String): Bitmap? { + val connection = runCatching { URL(url).openConnection() as HttpURLConnection }.getOrNull() ?: return null + connection.connectTimeout = IMAGE_TIMEOUT_MS + connection.readTimeout = IMAGE_TIMEOUT_MS + val download = imageLoader.submit { connection.inputStream.use { BitmapFactory.decodeStream(it) } } + return try { + download.get(IMAGE_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS) + } catch (e: Exception) { + download.cancel(true) + null + } finally { + connection.disconnect() + } + } + /** The ongoing notification the foreground service shows, on its own quiet channel. */ fun ongoingNotification(context: Context) = NotificationCompat.Builder(context, SERVICE_CHANNEL_ID) .also { createChannels(context) } diff --git a/library/src/main/java/io/appwrite/services/Storage.kt b/library/src/main/java/io/appwrite/services/Storage.kt index 6fe1f966..9f8f45b3 100644 --- a/library/src/main/java/io/appwrite/services/Storage.kt +++ b/library/src/main/java/io/appwrite/services/Storage.kt @@ -118,6 +118,7 @@ class Storage(client: Client) : Service(client) { paramName = paramName, idParamName = idParamName, onProgress = onProgress, + method = "POST", ) }