diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f1b0c..3197c21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,33 @@ # Change Log -## 29.1.0-rc.3 - -* Added: first background subscription auto-requests `POST_NOTIFICATIONS` (Android 13+) from the visible activity -* Fixed: `onOpen`/`onClose` callbacks fire correctly when joining or leaving the background host -* Fixed: notification taps are recorded after launching the app, so tap-opened screens land on top - -## 29.1.0-rc.2 - -* Added: `getInitialNotification()`, `onNotificationOpened()`, and the `PushNotificationOpened` data class (topic + data) -* Added: `onOpen`/`onClose` callbacks now fire for background push connections -* Added: push auth falls back to the client's cookie-store session -* Fixed: a foreground message with no live callback now still posts a notification -* Updated: notification taps route through an internal `PushOpenActivity` - -## 29.1.0-rc.1 - -* Updated: regenerated with sdk-generator 5.5.0; no SDK code changes - -## 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 +## 30.0.0 + +* Breaking: requests send `x-appwrite-response-format: 2.5.0`; responses follow the 2.5 shapes +* Breaking: missing response fields map to their documented defaults +* Breaking: `oauth2` `resource` params now take `List` on the authorize, PAR, device and token methods +* Breaking: `Push.subscribe` takes `notifyInForeground` before the callback; Java callers must pass it +* Breaking: removed the `Topic.any()` and `Topic.all()` wildcard builders and `ResolvedTopic`; `Push.subscribe` takes strings or `Topic` +* Added: `notifyInForeground` option on `Push.subscribe` +* Added: `PushOpenActivity` for opening notifications +* Added: `Query` aggregates (`count`, `countDistinct`, `sum`, `avg`, `min`, `max` and more), `groupBy`, `having`, `distinct`, joins, `relationship`, `covers`, `spatialEquals` and `page` +* Added: `Query.builder()` to chain queries; `build()` returns the query list +* Added: `ExecutionTrigger.EMAIL` +* Updated: `Topic.path` rejects levels containing a NUL character +* Fixed: a file exactly `CHUNK_SIZE` bytes long uploads in a single request + +## 29.2.0 + +* Added: `analytics.createEvent` in a new `Analytics` service to send tracking events +* Added: optional `state` on `account.createOAuth2Session` and `account.createOAuth2Token`, returned unchanged on the success and failure URLs +* Added: optional `membershipId` on `teams.createMembership` +* Fixed: `avatars.updatePhoto` sends `PUT`, so uploading a profile photo no longer returns 404 +* Fixed: OAuth2 query parameters (`success`, `failure`, `scopes`, `state`) are URL-encoded, so values containing `&`, `+` or `#` survive the redirect + +## 29.1.0 + +* Added: `account` passkey methods `listPasskeys`, `createPasskey`, `getPasskey`, `updatePasskey`, `deletePasskey` and `updatePasskeyVerification` +* Added: `account.createPasskeyToken` and `account.updatePasskeyToken` for passkey sign-in +* Added: `Passkey`, `PasskeyList`, `PasskeyChallenge` and `TokenSecret` models ## 29.0.0 diff --git a/README.md b/README.md index afcc18e..d045d7a 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.3.0-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-2.5.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.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. +**This SDK targets Appwrite server version 2.5.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:29.1.0-rc.3") +implementation("io.appwrite:sdk-for-android:30.0.0") ``` ### Maven @@ -49,7 +49,7 @@ Add this to your project's `pom.xml` file: io.appwrite sdk-for-android - 29.1.0-rc.3 + 30.0.0 ``` @@ -79,8 +79,10 @@ app every 15 to 60 seconds to reconnect, and the broker replays what was sent in While the app is not on screen, each message is also posted as a notification that opens 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` +title, body and image sent with `createPush`, falling back to the subscription's `title`, and to +the raw payload as the body for messages that are not a JSON object. A push with neither a title +nor a body, such as a data-only push, posts no notification; it still reaches your callback or +`PushReceiver`. On Android 13 and later it needs the `POST_NOTIFICATIONS` permission: the first background subscription asks for it from the visible activity (the one `Push` is created with, else the next one to resume), or call `Push.requestNotificationPermission(activity)` to ask yourself. Set its @@ -119,6 +121,13 @@ The SDK uses exact alarms on its own whenever the app may schedule them. To allo nothing to ask, including when the permission is not declared. The SDK never opens these screens on its own. +If `backgroundRestricted` is true, the user restricted the app's background usage and messages can +wait until the app is opened. Exact alarms don't lift it, and neither does foreground mode: Android +stops a restricted app's foreground service once it leaves the screen. Ask the user to choose +Unrestricted in the app's battery settings (Settings > Apps > your app > Battery); granting the +battery optimisation exemption also lets the SDK's alarms and jobs run, and `bestEffort` then +reflects only the exact-alarm state. + If the user force-stops the app (Settings > Force stop, and on some devices swiping it away from recents), Android cancels its alarms and jobs: nothing is delivered until the app is opened again, and the broker then replays what was sent meanwhile. @@ -232,7 +241,7 @@ val user = account.create( ### Type Safety with Models -The Appwrite Android SDK provides type safety when working with database documents through generic methods. Methods like `listDocuments`, `getDocument`, and others accept a `nestedType` parameter that allows you to specify your custom model type for full type safety. +The Appwrite Android SDK provides type safety when working with table rows through generic methods. Methods like `listRows`, `getRow`, and others accept a `nestedType` parameter that allows you to specify your custom model type for full type safety. **Kotlin:** ```kotlin @@ -245,16 +254,17 @@ data class Book( val isCheckedOut: Boolean ) -val databases = Databases(client) +val tablesDB = TablesDB(client) try { - val documents = databases.listDocuments( + val result = tablesDB.listRows( databaseId = "your-database-id", - collectionId = "your-collection-id", + tableId = "your-table-id", nestedType = Book::class.java // Pass in your custom model type ) - for (book in documents.documents) { + for (row in result.rows) { + val book = row.data Log.d("Appwrite", "Book: ${book.name} by ${book.author}") // Now you have full type safety } } catch (e: AppwriteException) { @@ -299,21 +309,24 @@ public class Book { public void setCheckedOut(boolean checkedOut) { isCheckedOut = checkedOut; } } -Databases databases = new Databases(client); - -try { - DocumentList documents = databases.listDocuments( - "your-database-id", - "your-collection-id", - Book.class // Pass in your custom model type - ); - - for (Book book : documents.getDocuments()) { - Log.d("Appwrite", "Book: " + book.getName() + " by " + book.getAuthor()); // Now you have full type safety - } -} catch (AppwriteException e) { - Log.e("Appwrite", e.getMessage() != null ? e.getMessage() : "Unknown error"); -} +TablesDB tablesDB = new TablesDB(client); + +tablesDB.listRows( + "your-database-id", + "your-table-id", + Book.class, // Pass in your custom model type + new CoroutineCallback<>((result, error) -> { + if (error != null) { + Log.e("Appwrite", error.getMessage() != null ? error.getMessage() : "Unknown error"); + return; + } + + for (Row row : result.getRows()) { + Book book = row.getData(); + Log.d("Appwrite", "Book: " + book.getName() + " by " + book.getAuthor()); // Now you have full type safety + } + }) +); ``` **Tip**: You can use the `appwrite types` command to automatically generate model definitions based on your Appwrite database schema. Learn more about [type generation](https://appwrite.io/docs/products/databases/type-generation). @@ -363,7 +376,7 @@ The Appwrite Android SDK raises an `AppwriteException` object with `message`, `c ```kotlin try { var user = account.create(ID.unique(),"email@example.com","password","Walter O'Brien") - Log.d("Appwrite user", user.toMap()) + Log.d("Appwrite user", user.toMap().toString()) } catch(e : AppwriteException) { e.printStackTrace() } 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 da54742..f2a07ef 100644 --- a/docs/examples/java/account/create-o-auth-2-session.md +++ b/docs/examples/java/account/create-o-auth-2-session.md @@ -17,6 +17,7 @@ account.createOAuth2Session( "https://example.com", // success (optional) "https://example.com", // failure (optional) List.of(), // scopes (optional) + "", // state (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); 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 ae68585..885c066 100644 --- a/docs/examples/java/account/create-o-auth-2-token.md +++ b/docs/examples/java/account/create-o-auth-2-token.md @@ -17,6 +17,7 @@ account.createOAuth2Token( "https://example.com", // success (optional) "https://example.com", // failure (optional) List.of(), // scopes (optional) + "", // state (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/java/account/create-passkey-token.md b/docs/examples/java/account/create-passkey-token.md new file mode 100644 index 0000000..12db5fe --- /dev/null +++ b/docs/examples/java/account/create-passkey-token.md @@ -0,0 +1,22 @@ +```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.createPasskeyToken(new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); +})); +``` diff --git a/docs/examples/java/account/create-passkey.md b/docs/examples/java/account/create-passkey.md new file mode 100644 index 0000000..39a9a29 --- /dev/null +++ b/docs/examples/java/account/create-passkey.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.createPasskey( + "", // passkeyId (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/delete-passkey.md b/docs/examples/java/account/delete-passkey.md new file mode 100644 index 0000000..a59d384 --- /dev/null +++ b/docs/examples/java/account/delete-passkey.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.deletePasskey( + "", // passkeyId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/get-passkey.md b/docs/examples/java/account/get-passkey.md new file mode 100644 index 0000000..5d988e8 --- /dev/null +++ b/docs/examples/java/account/get-passkey.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.getPasskey( + "", // passkeyId + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/list-passkeys.md b/docs/examples/java/account/list-passkeys.md new file mode 100644 index 0000000..6b04b1f --- /dev/null +++ b/docs/examples/java/account/list-passkeys.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.listPasskeys( + 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/update-passkey-token.md b/docs/examples/java/account/update-passkey-token.md new file mode 100644 index 0000000..6370aaa --- /dev/null +++ b/docs/examples/java/account/update-passkey-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.updatePasskeyToken( + "", // challengeId + Map.of("a", "b"), // credential + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/update-passkey-verification.md b/docs/examples/java/account/update-passkey-verification.md new file mode 100644 index 0000000..53eed3a --- /dev/null +++ b/docs/examples/java/account/update-passkey-verification.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.updatePasskeyVerification( + "", // passkeyId + Map.of("a", "b"), // credential + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/account/update-passkey.md b/docs/examples/java/account/update-passkey.md new file mode 100644 index 0000000..3cf1078 --- /dev/null +++ b/docs/examples/java/account/update-passkey.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.updatePasskey( + "", // passkeyId + "", // name + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + Log.d("Appwrite", result.toString()); + }) +); + +``` diff --git a/docs/examples/java/analytics/create-event.md b/docs/examples/java/analytics/create-event.md new file mode 100644 index 0000000..e2b8dbe --- /dev/null +++ b/docs/examples/java/analytics/create-event.md @@ -0,0 +1,38 @@ +```java +import android.util.Log; + +import io.appwrite.Client; +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.services.Analytics; + +Client client = new Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject(""); // Your project ID + +Analytics analytics = new Analytics(client); + +analytics.createEvent( + "", // propertyId + "", // name + "https://example.com", // url + "", // domain (optional) + "", // referrer (optional) + 0L, // screenWidth (optional) + "", // sessionHash (optional) + 0L, // scrollDepth (optional) + 0L, // engagementTime (optional) + List.of(), // props (optional) + "", // userId (optional) + "", // ip (optional) + "", // userAgent (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 index 99ed6b8..077546f 100644 --- a/docs/examples/java/oauth2/authorize-post.md +++ b/docs/examples/java/oauth2/authorize-post.md @@ -23,7 +23,7 @@ oauth2.authorizePost( "", // prompt (optional) 0L, // max_age (optional) "", // authorization_details (optional) - "", // resource (optional) + List.of(), // resource (optional) "", // audience (optional) "", // request_uri (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/java/oauth2/authorize.md b/docs/examples/java/oauth2/authorize.md index 62a452c..d5b72cf 100644 --- a/docs/examples/java/oauth2/authorize.md +++ b/docs/examples/java/oauth2/authorize.md @@ -23,7 +23,7 @@ oauth2.authorize( "", // prompt (optional) 0L, // max_age (optional) "", // authorization_details (optional) - "", // resource (optional) + List.of(), // resource (optional) "", // audience (optional) "", // request_uri (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/java/oauth2/create-device-authorization.md b/docs/examples/java/oauth2/create-device-authorization.md index 0b65a1a..c2fc4ad 100644 --- a/docs/examples/java/oauth2/create-device-authorization.md +++ b/docs/examples/java/oauth2/create-device-authorization.md @@ -15,7 +15,7 @@ oauth2.createDeviceAuthorization( "", // client_id (optional) "", // scope (optional) "", // authorization_details (optional) - "", // resource (optional) + List.of(), // resource (optional) "", // audience (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/oauth2/create-par.md b/docs/examples/java/oauth2/create-par.md index e65bdb5..e1f14de 100644 --- a/docs/examples/java/oauth2/create-par.md +++ b/docs/examples/java/oauth2/create-par.md @@ -23,7 +23,7 @@ oauth2.createPAR( "", // prompt (optional) 0L, // max_age (optional) "", // authorization_details (optional) - "", // resource (optional) + List.of(), // resource (optional) "", // audience (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/oauth2/create-token.md b/docs/examples/java/oauth2/create-token.md index 39b6fa2..5c808df 100644 --- a/docs/examples/java/oauth2/create-token.md +++ b/docs/examples/java/oauth2/create-token.md @@ -20,7 +20,7 @@ oauth2.createToken( "", // client_secret (optional) "", // code_verifier (optional) "https://example.com", // redirect_uri (optional) - "", // resource (optional) + List.of(), // resource (optional) "", // audience (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/java/teams/create-membership.md b/docs/examples/java/teams/create-membership.md index d7fba7c..b6d23a6 100644 --- a/docs/examples/java/teams/create-membership.md +++ b/docs/examples/java/teams/create-membership.md @@ -19,6 +19,7 @@ teams.createMembership( "+12065550100", // phone (optional) "https://example.com", // url (optional) "", // name (optional) + "", // membershipId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/kotlin/account/create-o-auth-2-session.md b/docs/examples/kotlin/account/create-o-auth-2-session.md index cde941c..c9b3a92 100644 --- a/docs/examples/kotlin/account/create-o-auth-2-session.md +++ b/docs/examples/kotlin/account/create-o-auth-2-session.md @@ -15,5 +15,6 @@ account.createOAuth2Session( success = "https://example.com", // (optional) failure = "https://example.com", // (optional) scopes = listOf(), // (optional) + state = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/account/create-o-auth-2-token.md b/docs/examples/kotlin/account/create-o-auth-2-token.md index 5e90564..3d5b0d7 100644 --- a/docs/examples/kotlin/account/create-o-auth-2-token.md +++ b/docs/examples/kotlin/account/create-o-auth-2-token.md @@ -15,5 +15,6 @@ account.createOAuth2Token( success = "https://example.com", // (optional) failure = "https://example.com", // (optional) scopes = listOf(), // (optional) + state = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/account/create-passkey-token.md b/docs/examples/kotlin/account/create-passkey-token.md new file mode 100644 index 0000000..52b6bf3 --- /dev/null +++ b/docs/examples/kotlin/account/create-passkey-token.md @@ -0,0 +1,13 @@ +```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.createPasskeyToken() +``` diff --git a/docs/examples/kotlin/account/create-passkey.md b/docs/examples/kotlin/account/create-passkey.md new file mode 100644 index 0000000..69a0651 --- /dev/null +++ b/docs/examples/kotlin/account/create-passkey.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.createPasskey( + passkeyId = "", // (optional) + name = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/account/delete-passkey.md b/docs/examples/kotlin/account/delete-passkey.md new file mode 100644 index 0000000..08bf8fd --- /dev/null +++ b/docs/examples/kotlin/account/delete-passkey.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.deletePasskey( + passkeyId = "", +) +``` diff --git a/docs/examples/kotlin/account/get-passkey.md b/docs/examples/kotlin/account/get-passkey.md new file mode 100644 index 0000000..ddf9e83 --- /dev/null +++ b/docs/examples/kotlin/account/get-passkey.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.getPasskey( + passkeyId = "", +) +``` diff --git a/docs/examples/kotlin/account/list-passkeys.md b/docs/examples/kotlin/account/list-passkeys.md new file mode 100644 index 0000000..74c8add --- /dev/null +++ b/docs/examples/kotlin/account/list-passkeys.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.listPasskeys( + queries = listOf(), // (optional) + total = false, // (optional) +) +``` diff --git a/docs/examples/kotlin/account/update-passkey-token.md b/docs/examples/kotlin/account/update-passkey-token.md new file mode 100644 index 0000000..f48e29f --- /dev/null +++ b/docs/examples/kotlin/account/update-passkey-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.updatePasskeyToken( + challengeId = "", + credential = mapOf( "a" to "b" ), +) +``` diff --git a/docs/examples/kotlin/account/update-passkey-verification.md b/docs/examples/kotlin/account/update-passkey-verification.md new file mode 100644 index 0000000..41c016a --- /dev/null +++ b/docs/examples/kotlin/account/update-passkey-verification.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.updatePasskeyVerification( + passkeyId = "", + credential = mapOf( "a" to "b" ), +) +``` diff --git a/docs/examples/kotlin/account/update-passkey.md b/docs/examples/kotlin/account/update-passkey.md new file mode 100644 index 0000000..c8c2bc3 --- /dev/null +++ b/docs/examples/kotlin/account/update-passkey.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.updatePasskey( + passkeyId = "", + name = "", +) +``` diff --git a/docs/examples/kotlin/analytics/create-event.md b/docs/examples/kotlin/analytics/create-event.md new file mode 100644 index 0000000..820e0ba --- /dev/null +++ b/docs/examples/kotlin/analytics/create-event.md @@ -0,0 +1,27 @@ +```kotlin +import io.appwrite.Client +import io.appwrite.coroutines.CoroutineCallback +import io.appwrite.services.Analytics + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint + .setProject("") // Your project ID + +val analytics = Analytics(client) + +val result = analytics.createEvent( + propertyId = "", + name = "", + url = "https://example.com", + domain = "", // (optional) + referrer = "", // (optional) + screenWidth = 0, // (optional) + sessionHash = "", // (optional) + scrollDepth = 0, // (optional) + engagementTime = 0, // (optional) + props = listOf(), // (optional) + userId = "", // (optional) + ip = "", // (optional) + userAgent = "", // (optional) +) +``` diff --git a/docs/examples/kotlin/oauth2/authorize-post.md b/docs/examples/kotlin/oauth2/authorize-post.md index 88bc4ee..ea49099 100644 --- a/docs/examples/kotlin/oauth2/authorize-post.md +++ b/docs/examples/kotlin/oauth2/authorize-post.md @@ -21,7 +21,7 @@ val result = oauth2.authorizePost( prompt = "", // (optional) maxAge = 0, // (optional) authorizationDetails = "", // (optional) - resource = "", // (optional) + resource = listOf(), // (optional) audience = "", // (optional) requestUri = "", // (optional) ) diff --git a/docs/examples/kotlin/oauth2/authorize.md b/docs/examples/kotlin/oauth2/authorize.md index 4d307e8..6c59834 100644 --- a/docs/examples/kotlin/oauth2/authorize.md +++ b/docs/examples/kotlin/oauth2/authorize.md @@ -21,7 +21,7 @@ val result = oauth2.authorize( prompt = "", // (optional) maxAge = 0, // (optional) authorizationDetails = "", // (optional) - resource = "", // (optional) + resource = listOf(), // (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 index ab307c3..d4fb9b0 100644 --- a/docs/examples/kotlin/oauth2/create-device-authorization.md +++ b/docs/examples/kotlin/oauth2/create-device-authorization.md @@ -13,7 +13,7 @@ val result = oauth2.createDeviceAuthorization( clientId = "", // (optional) scope = "", // (optional) authorizationDetails = "", // (optional) - resource = "", // (optional) + resource = listOf(), // (optional) audience = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/oauth2/create-par.md b/docs/examples/kotlin/oauth2/create-par.md index 752c64e..213096c 100644 --- a/docs/examples/kotlin/oauth2/create-par.md +++ b/docs/examples/kotlin/oauth2/create-par.md @@ -21,7 +21,7 @@ val result = oauth2.createPAR( prompt = "", // (optional) maxAge = 0, // (optional) authorizationDetails = "", // (optional) - resource = "", // (optional) + resource = listOf(), // (optional) audience = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/oauth2/create-token.md b/docs/examples/kotlin/oauth2/create-token.md index 5e23344..a0e4f20 100644 --- a/docs/examples/kotlin/oauth2/create-token.md +++ b/docs/examples/kotlin/oauth2/create-token.md @@ -18,7 +18,7 @@ val result = oauth2.createToken( clientSecret = "", // (optional) codeVerifier = "", // (optional) redirectUri = "https://example.com", // (optional) - resource = "", // (optional) + resource = listOf(), // (optional) audience = "", // (optional) ) ``` diff --git a/docs/examples/kotlin/teams/create-membership.md b/docs/examples/kotlin/teams/create-membership.md index 219119a..2f73c03 100644 --- a/docs/examples/kotlin/teams/create-membership.md +++ b/docs/examples/kotlin/teams/create-membership.md @@ -17,5 +17,6 @@ val result = teams.createMembership( phone = "+12065550100", // (optional) url = "https://example.com", // (optional) name = "", // (optional) + membershipId = "", // (optional) ) ``` diff --git a/library/src/main/java/io/appwrite/Client.kt b/library/src/main/java/io/appwrite/Client.kt index 3df991a..ecc12c3 100644 --- a/library/src/main/java/io/appwrite/Client.kt +++ b/library/src/main/java/io/appwrite/Client.kt @@ -101,8 +101,8 @@ class Client @JvmOverloads constructor( "x-sdk-name" to "Android", "x-sdk-platform" to "client", "x-sdk-language" to "android", - "x-sdk-version" to "29.1.0-rc.3", - "x-appwrite-response-format" to "2.3.0" + "x-sdk-version" to "30.0.0", + "x-appwrite-response-format" to "2.5.0" ) config = mutableMapOf() @@ -434,7 +434,10 @@ class Client @JvmOverloads constructor( headers: Map = mapOf(), params: Map = mapOf(), ): Request { - val filteredParams = params.filterValues { it != null } + // flatten() resolves Query/Query.Builder values before the null filter, + // so a builder that renders to null is dropped like any other null. + val flattenedParams = params.mapValues { flatten(it.value) } + val filteredParams = flattenedParams.filterValues { it != null } val requestHeaders = this.headers.toHeaders().newBuilder() .addAll(headers.toHeaders()) @@ -497,7 +500,7 @@ class Client @JvmOverloads constructor( } builder.build() } else { - params + flattenedParams .toJsonRequestBody() .toRequestBody("application/json".toMediaType()) } @@ -841,4 +844,44 @@ class Client @JvmOverloads constructor( } }) } + + fun flatten(value: Any?): Any? { + if (value is Query.Builder) { + return value.build() + } + if (value is Query) { + return value.toString() + } + + val items = when (value) { + is Array<*> -> value.asList() + is Collection<*> -> value + else -> return value + } + + val out = ArrayList(items.size) + var changed = false + for (item in items) { + val flat = flatten(item) + if (item is Query.Builder || item is Query) { + if (flat is Collection<*>) { + out.addAll(flat) + } else { + out.add(flat) + } + changed = true + continue + } + if ((item is Array<*> || item is Collection<*>) && flat is Collection<*> && flat.all { it is String }) { + out.addAll(flat) + changed = true + continue + } + if (flat !== item) { + changed = true + } + out.add(flat) + } + return if (changed || value is Array<*>) out else value + } } diff --git a/library/src/main/java/io/appwrite/Query.kt b/library/src/main/java/io/appwrite/Query.kt index 23f100d..6be7a6d 100644 --- a/library/src/main/java/io/appwrite/Query.kt +++ b/library/src/main/java/io/appwrite/Query.kt @@ -377,6 +377,15 @@ class Query( */ fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson>() }).toJson() + /** + * Constrain populated related documents on a relationship attribute. + * + * @param attribute The relationship attribute to query. + * @param queries Filter, order, select, and pagination queries applied to related documents. + * @returns The query string. + */ + fun relationship(attribute: String, queries: List) = Query("relationship", attribute, queries.map { it.fromJson>() }).toJson() + /** * Filter resources where attribute is at a specific distance from the given coordinates. * @@ -520,6 +529,377 @@ class Query( */ fun notTouches(attribute: String, values: List) = Query("notTouches", attribute, listOf(values)).toJson() + /** + * Count rows of [attribute], optionally aliased. + * + * @param attribute The attribute to count. Defaults to `*`. + * @param alias Optional result alias. + * @returns The query string. + */ + fun count(attribute: String = "*", alias: String = "") = Query("count", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Count distinct values of [attribute], optionally aliased. + * + * @param attribute The attribute to count. + * @param alias Optional result alias. + * @returns The query string. + */ + fun countDistinct(attribute: String, alias: String = "") = Query("countDistinct", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Sum values of [attribute], optionally aliased. + * + * @param attribute The attribute to sum. + * @param alias Optional result alias. + * @returns The query string. + */ + fun sum(attribute: String, alias: String = "") = Query("sum", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Average values of [attribute], optionally aliased. + * + * @param attribute The attribute to average. + * @param alias Optional result alias. + * @returns The query string. + */ + fun avg(attribute: String, alias: String = "") = Query("avg", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Minimum value of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun min(attribute: String, alias: String = "") = Query("min", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Maximum value of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun max(attribute: String, alias: String = "") = Query("max", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Standard deviation of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun stddev(attribute: String, alias: String = "") = Query("stddev", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Population standard deviation of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun stddevPop(attribute: String, alias: String = "") = Query("stddevPop", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Sample standard deviation of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun stddevSamp(attribute: String, alias: String = "") = Query("stddevSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Variance of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun variance(attribute: String, alias: String = "") = Query("variance", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Population variance of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun varPop(attribute: String, alias: String = "") = Query("varPop", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Sample variance of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun varSamp(attribute: String, alias: String = "") = Query("varSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Bitwise AND of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun bitAnd(attribute: String, alias: String = "") = Query("bitAnd", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Bitwise OR of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun bitOr(attribute: String, alias: String = "") = Query("bitOr", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Bitwise XOR of [attribute], optionally aliased. + * + * @param attribute The attribute to inspect. + * @param alias Optional result alias. + * @returns The query string. + */ + fun bitXor(attribute: String, alias: String = "") = Query("bitXor", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Group results by [attributes]. + * + * @param attributes Attributes to group by. + * @returns The query string. + */ + fun groupBy(attributes: List) = Query("groupBy", null, attributes).toJson() + + /** + * Filter grouped results. [queries] are nested like `and`/`or`. + * + * @param queries Nested query strings. + * @returns The query string. + */ + fun having(queries: List) = Query("having", null, queries.map { it.fromJson>() }).toJson() + + /** + * Select distinct rows. + * + * @returns The query string. + */ + fun distinct() = Query("distinct").toJson() + + /** + * Column-to-column join ON condition. + * + * @param left Left-hand column. + * @param right Right-hand column. + * @param operator Comparison operator. Defaults to `=`. + * @returns The query string. + */ + fun on(left: String, right: String, operator: String = "=") = Query("on", values = listOf(left, operator, right)).toJson() + + /** + * Inner join. [table] is the joined collection/table ID. + * + * @param table Joined table ID. + * @param left Left-hand join column. + * @param right Right-hand join column. + * @param operator Comparison operator. Defaults to `=`. + * @param alias Optional table alias. + * @returns The query string. + */ + fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("join", table, left, right, operator, alias) + + /** + * Inner join with nested ON queries and no alias. + * + * @param table Joined table ID. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun join(table: String, on: List) = nestedJoin("join", table, "", on) + + /** + * Inner join with nested ON queries and an alias. + * + * @param table Joined table ID. + * @param alias Table alias. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun join(table: String, alias: String, on: List) = nestedJoin("join", table, alias, on) + + /** + * Left join. [table] is the joined collection/table ID. + * + * @param table Joined table ID. + * @param left Left-hand join column. + * @param right Right-hand join column. + * @param operator Comparison operator. Defaults to `=`. + * @param alias Optional table alias. + * @returns The query string. + */ + fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("leftJoin", table, left, right, operator, alias) + + /** + * Left join with nested ON queries and no alias. + * + * @param table Joined table ID. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun leftJoin(table: String, on: List) = nestedJoin("leftJoin", table, "", on) + + /** + * Left join with nested ON queries and an alias. + * + * @param table Joined table ID. + * @param alias Table alias. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun leftJoin(table: String, alias: String, on: List) = nestedJoin("leftJoin", table, alias, on) + + /** + * Right join. [table] is the joined collection/table ID. + * + * @param table Joined table ID. + * @param left Left-hand join column. + * @param right Right-hand join column. + * @param operator Comparison operator. Defaults to `=`. + * @param alias Optional table alias. + * @returns The query string. + */ + fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("rightJoin", table, left, right, operator, alias) + + /** + * Right join with nested ON queries and no alias. + * + * @param table Joined table ID. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun rightJoin(table: String, on: List) = nestedJoin("rightJoin", table, "", on) + + /** + * Right join with nested ON queries and an alias. + * + * @param table Joined table ID. + * @param alias Table alias. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun rightJoin(table: String, alias: String, on: List) = nestedJoin("rightJoin", table, alias, on) + + /** + * Full outer join. [table] is the joined collection/table ID. + * + * @param table Joined table ID. + * @param left Left-hand join column. + * @param right Right-hand join column. + * @param operator Comparison operator. Defaults to `=`. + * @param alias Optional table alias. + * @returns The query string. + */ + fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("fullOuterJoin", table, left, right, operator, alias) + + /** + * Full outer join with nested ON queries and no alias. + * + * @param table Joined table ID. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun fullOuterJoin(table: String, on: List) = nestedJoin("fullOuterJoin", table, "", on) + + /** + * Full outer join with nested ON queries and an alias. + * + * @param table Joined table ID. + * @param alias Table alias. + * @param on Nested ON queries, including [on]. + * @returns The query string. + */ + fun fullOuterJoin(table: String, alias: String, on: List) = nestedJoin("fullOuterJoin", table, alias, on) + + /** + * Cross join. [table] is the joined collection/table ID. + * + * @param table Joined table ID. + * @param alias Optional table alias. + * @returns The query string. + */ + fun crossJoin(table: String, alias: String = "") = Query("crossJoin", table, if (alias.isNotEmpty()) listOf(alias) else null).toJson() + + /** + * Filter resources where [attribute] covers the given geometry. + * + * @param attribute The attribute to filter on. + * @param values The coordinate values. + * @returns The query string. + */ + fun covers(attribute: String, values: List) = Query("covers", attribute, listOf(values)).toJson() + + /** + * Filter resources where [attribute] does not cover the given geometry. + * + * @param attribute The attribute to filter on. + * @param values The coordinate values. + * @returns The query string. + */ + fun notCovers(attribute: String, values: List) = Query("notCovers", attribute, listOf(values)).toJson() + + /** + * Filter resources where [attribute] is spatially equal to the given geometry. + * + * @param attribute The attribute to filter on. + * @param values The coordinate values. + * @returns The query string. + */ + fun spatialEquals(attribute: String, values: List) = Query("spatialEquals", attribute, listOf(values)).toJson() + + /** + * Filter resources where [attribute] is not spatially equal to the given geometry. + * + * @param attribute The attribute to filter on. + * @param values The coordinate values. + * @returns The query string. + */ + fun notSpatialEquals(attribute: String, values: List) = Query("notSpatialEquals", attribute, listOf(values)).toJson() + + /** + * Return limit and offset queries for 1-based page pagination. + * + * @param page 1-based page number. + * @param perPage Rows per page. Defaults to 25. + * @returns Limit and offset query strings. + */ + fun page(page: Int, perPage: Int = 25): List { + require(page >= 1) { "Page must be >= 1, got $page" } + require(perPage >= 1) { "Per page must be >= 1, got $perPage" } + return listOf(limit(perPage), offset(Math.multiplyExact(page - 1, perPage))) + } + + /** + * Start a fluent query builder. + * + * @returns A new query builder. + */ + fun builder() = Builder() + + private fun joinQuery(method: String, table: String, left: String, right: String, operator: String, alias: String): String { + val values = if (alias.isNotEmpty()) listOf(left, operator, right, alias) else listOf(left, operator, right) + return Query(method, table, values).toJson() + } + + private fun nestedJoin(method: String, table: String, alias: String, on: List): String { + val values = mutableListOf() + if (alias.isNotEmpty()) { + values.add(alias) + } + on.forEach { values.add(it.fromJson>()) } + return Query(method, table, values).toJson() + } + /** * Parse the value to a list of values. * @@ -533,4 +913,198 @@ class Query( } } } + + class Builder { + private val queries = mutableListOf() + + fun build(): List = queries.toList() + + fun equal(attribute: String, value: Any) = push(Query.equal(attribute, value)) + + fun notEqual(attribute: String, value: Any) = push(Query.notEqual(attribute, value)) + + fun lessThan(attribute: String, value: Any) = push(Query.lessThan(attribute, value)) + + fun lessThanEqual(attribute: String, value: Any) = push(Query.lessThanEqual(attribute, value)) + + fun greaterThan(attribute: String, value: Any) = push(Query.greaterThan(attribute, value)) + + fun greaterThanEqual(attribute: String, value: Any) = push(Query.greaterThanEqual(attribute, value)) + + fun search(attribute: String, value: String) = push(Query.search(attribute, value)) + + fun isNull(attribute: String) = push(Query.isNull(attribute)) + + fun isNotNull(attribute: String) = push(Query.isNotNull(attribute)) + + fun between(attribute: String, start: Any, end: Any) = push(Query.between(attribute, start, end)) + + fun startsWith(attribute: String, value: String) = push(Query.startsWith(attribute, value)) + + fun endsWith(attribute: String, value: String) = push(Query.endsWith(attribute, value)) + + fun select(attributes: List) = push(Query.select(attributes)) + + fun cursorAfter(documentId: String) = push(Query.cursorAfter(documentId)) + + fun cursorBefore(documentId: String) = push(Query.cursorBefore(documentId)) + + fun orderAsc(attribute: String) = push(Query.orderAsc(attribute)) + + fun orderDesc(attribute: String) = push(Query.orderDesc(attribute)) + + fun orderRandom() = push(Query.orderRandom()) + + fun limit(limit: Int) = push(Query.limit(limit)) + + fun offset(offset: Int) = push(Query.offset(offset)) + + fun contains(attribute: String, value: Any) = push(Query.contains(attribute, value)) + + fun containsAny(attribute: String, value: List) = push(Query.containsAny(attribute, value)) + + fun containsAll(attribute: String, value: List) = push(Query.containsAll(attribute, value)) + + fun notContains(attribute: String, value: Any) = push(Query.notContains(attribute, value)) + + fun notSearch(attribute: String, value: String) = push(Query.notSearch(attribute, value)) + + fun notBetween(attribute: String, start: Any, end: Any) = push(Query.notBetween(attribute, start, end)) + + fun notStartsWith(attribute: String, value: String) = push(Query.notStartsWith(attribute, value)) + + fun notEndsWith(attribute: String, value: String) = push(Query.notEndsWith(attribute, value)) + + fun regex(attribute: String, pattern: String) = push(Query.regex(attribute, pattern)) + + fun exists(attributes: List) = push(Query.exists(attributes)) + + fun notExists(attributes: List) = push(Query.notExists(attributes)) + + fun createdBefore(value: String) = push(Query.createdBefore(value)) + + fun createdAfter(value: String) = push(Query.createdAfter(value)) + + fun createdBetween(start: String, end: String) = push(Query.createdBetween(start, end)) + + fun updatedBefore(value: String) = push(Query.updatedBefore(value)) + + fun updatedAfter(value: String) = push(Query.updatedAfter(value)) + + fun updatedBetween(start: String, end: String) = push(Query.updatedBetween(start, end)) + + fun or(queries: List) = push(Query.or(queries)) + + fun and(queries: List) = push(Query.and(queries)) + + fun elemMatch(attribute: String, queries: List) = push(Query.elemMatch(attribute, queries)) + + fun relationship(attribute: String, queries: List) = push(Query.relationship(attribute, queries)) + + fun distanceEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceEqual(attribute, values, distance, meters)) + + fun distanceNotEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceNotEqual(attribute, values, distance, meters)) + + fun distanceGreaterThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceGreaterThan(attribute, values, distance, meters)) + + fun distanceLessThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = push(Query.distanceLessThan(attribute, values, distance, meters)) + + fun intersects(attribute: String, values: List) = push(Query.intersects(attribute, values)) + + fun notIntersects(attribute: String, values: List) = push(Query.notIntersects(attribute, values)) + + fun crosses(attribute: String, values: List) = push(Query.crosses(attribute, values)) + + fun notCrosses(attribute: String, values: List) = push(Query.notCrosses(attribute, values)) + + fun overlaps(attribute: String, values: List) = push(Query.overlaps(attribute, values)) + + fun notOverlaps(attribute: String, values: List) = push(Query.notOverlaps(attribute, values)) + + fun touches(attribute: String, values: List) = push(Query.touches(attribute, values)) + + fun notTouches(attribute: String, values: List) = push(Query.notTouches(attribute, values)) + + fun count(attribute: String = "*", alias: String = "") = push(Query.count(attribute, alias)) + + fun countDistinct(attribute: String, alias: String = "") = push(Query.countDistinct(attribute, alias)) + + fun sum(attribute: String, alias: String = "") = push(Query.sum(attribute, alias)) + + fun avg(attribute: String, alias: String = "") = push(Query.avg(attribute, alias)) + + fun min(attribute: String, alias: String = "") = push(Query.min(attribute, alias)) + + fun max(attribute: String, alias: String = "") = push(Query.max(attribute, alias)) + + fun stddev(attribute: String, alias: String = "") = push(Query.stddev(attribute, alias)) + + fun stddevPop(attribute: String, alias: String = "") = push(Query.stddevPop(attribute, alias)) + + fun stddevSamp(attribute: String, alias: String = "") = push(Query.stddevSamp(attribute, alias)) + + fun variance(attribute: String, alias: String = "") = push(Query.variance(attribute, alias)) + + fun varPop(attribute: String, alias: String = "") = push(Query.varPop(attribute, alias)) + + fun varSamp(attribute: String, alias: String = "") = push(Query.varSamp(attribute, alias)) + + fun bitAnd(attribute: String, alias: String = "") = push(Query.bitAnd(attribute, alias)) + + fun bitOr(attribute: String, alias: String = "") = push(Query.bitOr(attribute, alias)) + + fun bitXor(attribute: String, alias: String = "") = push(Query.bitXor(attribute, alias)) + + fun groupBy(attributes: List) = push(Query.groupBy(attributes)) + + fun having(queries: List) = push(Query.having(queries)) + + fun distinct() = push(Query.distinct()) + + fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = push(Query.join(table, left, right, operator, alias)) + + fun join(table: String, on: List) = push(Query.join(table, on)) + + fun join(table: String, alias: String, on: List) = push(Query.join(table, alias, on)) + + fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = push(Query.leftJoin(table, left, right, operator, alias)) + + fun leftJoin(table: String, on: List) = push(Query.leftJoin(table, on)) + + fun leftJoin(table: String, alias: String, on: List) = push(Query.leftJoin(table, alias, on)) + + fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = push(Query.rightJoin(table, left, right, operator, alias)) + + fun rightJoin(table: String, on: List) = push(Query.rightJoin(table, on)) + + fun rightJoin(table: String, alias: String, on: List) = push(Query.rightJoin(table, alias, on)) + + fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = push(Query.fullOuterJoin(table, left, right, operator, alias)) + + fun fullOuterJoin(table: String, on: List) = push(Query.fullOuterJoin(table, on)) + + fun fullOuterJoin(table: String, alias: String, on: List) = push(Query.fullOuterJoin(table, alias, on)) + + fun crossJoin(table: String, alias: String = "") = push(Query.crossJoin(table, alias)) + + fun vectorDot(attribute: String, vector: List) = push(Query.vectorDot(attribute, vector)) + + fun vectorCosine(attribute: String, vector: List) = push(Query.vectorCosine(attribute, vector)) + + fun vectorEuclidean(attribute: String, vector: List) = push(Query.vectorEuclidean(attribute, vector)) + + fun covers(attribute: String, values: List) = push(Query.covers(attribute, values)) + + fun notCovers(attribute: String, values: List) = push(Query.notCovers(attribute, values)) + + fun spatialEquals(attribute: String, values: List) = push(Query.spatialEquals(attribute, values)) + + fun notSpatialEquals(attribute: String, values: List) = push(Query.notSpatialEquals(attribute, values)) + + fun page(page: Int, perPage: Int = 25) = push(Query.page(page, perPage)) + + private fun push(query: String) = apply { queries.add(query) } + + private fun push(query: List) = apply { queries.addAll(query) } + } } diff --git a/library/src/main/java/io/appwrite/Topic.kt b/library/src/main/java/io/appwrite/Topic.kt index 8945da0..614dca3 100644 --- a/library/src/main/java/io/appwrite/Topic.kt +++ b/library/src/main/java/io/appwrite/Topic.kt @@ -2,7 +2,7 @@ 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). + * the level separator `/` or the MQTT wildcards `+` and `#`. */ private fun levelsOf(levels: List): List { require(levels.isNotEmpty()) { "path() needs at least one level" } @@ -12,10 +12,9 @@ private fun levelsOf(levels: List): List { 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" } + require(!level.contains("+")) { "\"$level\" contains \"+\", not allowed in a topic" } + require(!level.contains("#")) { "\"$level\" contains \"#\", not allowed in a topic" } + require(!level.contains('\u0000')) { "level at index $index contains a NUL character" } } return levels.toList() } @@ -25,13 +24,9 @@ private fun levelsOf(levels: List): List { * * ``` * 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/#" + * Topic.path(listOf("org", orgId)).path(listOf("logs")) // "org/42/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( @@ -40,29 +35,10 @@ class Topic private constructor( /** 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/enums/ExecutionTrigger.kt b/library/src/main/java/io/appwrite/enums/ExecutionTrigger.kt index 3ba84bb..5cb0112 100644 --- a/library/src/main/java/io/appwrite/enums/ExecutionTrigger.kt +++ b/library/src/main/java/io/appwrite/enums/ExecutionTrigger.kt @@ -8,7 +8,9 @@ enum class ExecutionTrigger(val value: String) { @SerializedName("schedule") SCHEDULE("schedule"), @SerializedName("event") - EVENT("event"); + EVENT("event"), + @SerializedName("email") + EMAIL("email"); override fun toString() = value } diff --git a/library/src/main/java/io/appwrite/models/Account.kt b/library/src/main/java/io/appwrite/models/Account.kt index 38865ff..2d46114 100644 --- a/library/src/main/java/io/appwrite/models/Account.kt +++ b/library/src/main/java/io/appwrite/models/Account.kt @@ -237,28 +237,28 @@ data class Account( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + registration = (if (map.containsKey("registration")) map["registration"] else "") as String, + status = (if (map.containsKey("status")) map["status"] else true) as Boolean, + labels = (if (map.containsKey("labels")) map["labels"] else listOf()) as List, + passwordUpdate = (if (map.containsKey("passwordUpdate")) map["passwordUpdate"] else "") as String, + email = (if (map.containsKey("email")) map["email"] else "") as String, + phone = (if (map.containsKey("phone")) map["phone"] else "") as String, + emailVerification = (if (map.containsKey("emailVerification")) map["emailVerification"] else false) 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, + phoneVerification = (if (map.containsKey("phoneVerification")) map["phoneVerification"] else false) as Boolean, + mfa = (if (map.containsKey("mfa")) map["mfa"] else false) 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, + targets = ((if (map.containsKey("targets")) map["targets"] else listOf()) as List>).map { Target.from(map = it) }, + accessedAt = (if (map.containsKey("accessedAt")) map["accessedAt"] else "") as String, impersonator = map["impersonator"] as? Boolean, impersonatorUserId = map["impersonatorUserId"] as? String, ) diff --git a/library/src/main/java/io/appwrite/models/AlgoArgon2.kt b/library/src/main/java/io/appwrite/models/AlgoArgon2.kt index 3977332..1d26dfd 100644 --- a/library/src/main/java/io/appwrite/models/AlgoArgon2.kt +++ b/library/src/main/java/io/appwrite/models/AlgoArgon2.kt @@ -44,10 +44,10 @@ data class AlgoArgon2( fun from( map: Map, ) = AlgoArgon2( - type = map["type"] as String, - memoryCost = (map["memoryCost"] as Number).toLong(), - timeCost = (map["timeCost"] as Number).toLong(), - threads = (map["threads"] as Number).toLong(), + type = (if (map.containsKey("type")) map["type"] else "argon2") as String, + memoryCost = ((if (map.containsKey("memoryCost")) map["memoryCost"] else 0) as Number).toLong(), + timeCost = ((if (map.containsKey("timeCost")) map["timeCost"] else 0) as Number).toLong(), + threads = ((if (map.containsKey("threads")) map["threads"] else 0) as Number).toLong(), ) } } diff --git a/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt b/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt index 3831780..2a99dad 100644 --- a/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt +++ b/library/src/main/java/io/appwrite/models/AlgoBcrypt.kt @@ -23,7 +23,7 @@ data class AlgoBcrypt( fun from( map: Map, ) = AlgoBcrypt( - type = map["type"] as String, + type = (if (map.containsKey("type")) map["type"] else "bcrypt") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/AlgoMd5.kt b/library/src/main/java/io/appwrite/models/AlgoMd5.kt index e46eb99..d683ea7 100644 --- a/library/src/main/java/io/appwrite/models/AlgoMd5.kt +++ b/library/src/main/java/io/appwrite/models/AlgoMd5.kt @@ -23,7 +23,7 @@ data class AlgoMd5( fun from( map: Map, ) = AlgoMd5( - type = map["type"] as String, + type = (if (map.containsKey("type")) map["type"] else "md5") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/AlgoPhpass.kt b/library/src/main/java/io/appwrite/models/AlgoPhpass.kt index d457a81..3eba6ca 100644 --- a/library/src/main/java/io/appwrite/models/AlgoPhpass.kt +++ b/library/src/main/java/io/appwrite/models/AlgoPhpass.kt @@ -23,7 +23,7 @@ data class AlgoPhpass( fun from( map: Map, ) = AlgoPhpass( - type = map["type"] as String, + type = (if (map.containsKey("type")) map["type"] else "phpass") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/AlgoScrypt.kt b/library/src/main/java/io/appwrite/models/AlgoScrypt.kt index 9214704..789061f 100644 --- a/library/src/main/java/io/appwrite/models/AlgoScrypt.kt +++ b/library/src/main/java/io/appwrite/models/AlgoScrypt.kt @@ -51,11 +51,11 @@ data class AlgoScrypt( fun from( map: Map, ) = AlgoScrypt( - type = map["type"] as String, - costCpu = (map["costCpu"] as Number).toLong(), - costMemory = (map["costMemory"] as Number).toLong(), - costParallel = (map["costParallel"] as Number).toLong(), - length = (map["length"] as Number).toLong(), + type = (if (map.containsKey("type")) map["type"] else "scrypt") as String, + costCpu = ((if (map.containsKey("costCpu")) map["costCpu"] else 8) as Number).toLong(), + costMemory = ((if (map.containsKey("costMemory")) map["costMemory"] else 14) as Number).toLong(), + costParallel = ((if (map.containsKey("costParallel")) map["costParallel"] else 1) as Number).toLong(), + length = ((if (map.containsKey("length")) map["length"] else 64) as Number).toLong(), ) } } diff --git a/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt b/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt index 539828a..ff306fa 100644 --- a/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt +++ b/library/src/main/java/io/appwrite/models/AlgoScryptModified.kt @@ -44,10 +44,10 @@ data class AlgoScryptModified( fun from( map: Map, ) = AlgoScryptModified( - type = map["type"] as String, - salt = map["salt"] as String, - saltSeparator = map["saltSeparator"] as String, - signerKey = map["signerKey"] as String, + type = (if (map.containsKey("type")) map["type"] else "scryptMod") as String, + salt = (if (map.containsKey("salt")) map["salt"] else "") as String, + saltSeparator = (if (map.containsKey("saltSeparator")) map["saltSeparator"] else "") as String, + signerKey = (if (map.containsKey("signerKey")) map["signerKey"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/AlgoSha.kt b/library/src/main/java/io/appwrite/models/AlgoSha.kt index 45fb010..5c52f08 100644 --- a/library/src/main/java/io/appwrite/models/AlgoSha.kt +++ b/library/src/main/java/io/appwrite/models/AlgoSha.kt @@ -23,7 +23,7 @@ data class AlgoSha( fun from( map: Map, ) = AlgoSha( - type = map["type"] as String, + type = (if (map.containsKey("type")) map["type"] else "sha") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/App.kt b/library/src/main/java/io/appwrite/models/App.kt index f4a715c..9125cdc 100644 --- a/library/src/main/java/io/appwrite/models/App.kt +++ b/library/src/main/java/io/appwrite/models/App.kt @@ -198,32 +198,32 @@ data class App( 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) }, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + description = (if (map.containsKey("description")) map["description"] else "") as String, + clientUri = (if (map.containsKey("clientUri")) map["clientUri"] else "") as String, + logoUri = (if (map.containsKey("logoUri")) map["logoUri"] else "") as String, + privacyPolicyUrl = (if (map.containsKey("privacyPolicyUrl")) map["privacyPolicyUrl"] else "") as String, + termsUrl = (if (map.containsKey("termsUrl")) map["termsUrl"] else "") as String, + contacts = (if (map.containsKey("contacts")) map["contacts"] else listOf()) as List, + tagline = (if (map.containsKey("tagline")) map["tagline"] else "") as String, + tags = (if (map.containsKey("tags")) map["tags"] else listOf()) as List, + labels = (if (map.containsKey("labels")) map["labels"] else listOf()) as List, + images = (if (map.containsKey("images")) map["images"] else listOf()) as List, + supportUrl = (if (map.containsKey("supportUrl")) map["supportUrl"] else "") as String, + dataDeletionUrl = (if (map.containsKey("dataDeletionUrl")) map["dataDeletionUrl"] else "") as String, + redirectUris = (if (map.containsKey("redirectUris")) map["redirectUris"] else listOf()) as List, + postLogoutRedirectUris = (if (map.containsKey("postLogoutRedirectUris")) map["postLogoutRedirectUris"] else listOf()) as List, + enabled = (if (map.containsKey("enabled")) map["enabled"] else true) as Boolean, + type = (if (map.containsKey("type")) map["type"] else "confidential") as String, + deviceFlow = (if (map.containsKey("deviceFlow")) map["deviceFlow"] else false) as Boolean, + teamId = (if (map.containsKey("teamId")) map["teamId"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + installationScopes = (if (map.containsKey("installationScopes")) map["installationScopes"] else listOf()) as List, + installationRedirectUrl = (if (map.containsKey("installationRedirectUrl")) map["installationRedirectUrl"] else "") as String, + secrets = ((if (map.containsKey("secrets")) map["secrets"] else listOf()) as List>).map { AppSecret.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/AppInstallation.kt b/library/src/main/java/io/appwrite/models/AppInstallation.kt index 46bf907..3c6d1f6 100644 --- a/library/src/main/java/io/appwrite/models/AppInstallation.kt +++ b/library/src/main/java/io/appwrite/models/AppInstallation.kt @@ -86,15 +86,15 @@ data class AppInstallation( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + appId = (if (map.containsKey("appId")) map["appId"] else "") as String, + teamId = (if (map.containsKey("teamId")) map["teamId"] else "") as String, + scopes = (if (map.containsKey("scopes")) map["scopes"] else listOf()) as List, + authorizationDetails = (if (map.containsKey("authorizationDetails")) map["authorizationDetails"] else listOf()) as List>, + createdById = (if (map.containsKey("createdById")) map["createdById"] else "") as String, + createdByName = (if (map.containsKey("createdByName")) map["createdByName"] else "") 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 index f7434cf..8006d98 100644 --- a/library/src/main/java/io/appwrite/models/AppInstallationList.kt +++ b/library/src/main/java/io/appwrite/models/AppInstallationList.kt @@ -30,8 +30,8 @@ data class AppInstallationList( fun from( map: Map, ) = AppInstallationList( - total = (map["total"] as Number).toLong(), - installations = (map["installations"] as List>).map { AppInstallation.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + installations = ((if (map.containsKey("installations")) map["installations"] else listOf()) as List>).map { AppInstallation.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 index f59e8d5..7348cd3 100644 --- a/library/src/main/java/io/appwrite/models/AppKey.kt +++ b/library/src/main/java/io/appwrite/models/AppKey.kt @@ -79,14 +79,14 @@ data class AppKey( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + appId = (if (map.containsKey("appId")) map["appId"] else "") as String, + secret = (if (map.containsKey("secret")) map["secret"] else "") as String, + hint = (if (map.containsKey("hint")) map["hint"] else "") as String, + createdById = (if (map.containsKey("createdById")) map["createdById"] else "") as String, + createdByName = (if (map.containsKey("createdByName")) map["createdByName"] else "") 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 index ea4b1cc..854af04 100644 --- a/library/src/main/java/io/appwrite/models/AppKeyList.kt +++ b/library/src/main/java/io/appwrite/models/AppKeyList.kt @@ -30,8 +30,8 @@ data class AppKeyList( fun from( map: Map, ) = AppKeyList( - total = (map["total"] as Number).toLong(), - keys = (map["keys"] as List>).map { AppKey.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + keys = ((if (map.containsKey("keys")) map["keys"] else listOf()) 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 index 4cc7fe9..dbacafc 100644 --- a/library/src/main/java/io/appwrite/models/AppScope.kt +++ b/library/src/main/java/io/appwrite/models/AppScope.kt @@ -51,11 +51,11 @@ data class AppScope( 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, + value = (if (map.containsKey("value")) map["value"] else "") as String, + description = (if (map.containsKey("description")) map["description"] else "") as String, + type = (if (map.containsKey("type")) map["type"] else "") as String, + category = (if (map.containsKey("category")) map["category"] else "") as String, + deprecated = (if (map.containsKey("deprecated")) map["deprecated"] else false) as Boolean, ) } } diff --git a/library/src/main/java/io/appwrite/models/AppScopeList.kt b/library/src/main/java/io/appwrite/models/AppScopeList.kt index cbcf91f..76bc736 100644 --- a/library/src/main/java/io/appwrite/models/AppScopeList.kt +++ b/library/src/main/java/io/appwrite/models/AppScopeList.kt @@ -30,8 +30,8 @@ data class AppScopeList( fun from( map: Map, ) = AppScopeList( - total = (map["total"] as Number).toLong(), - scopes = (map["scopes"] as List>).map { AppScope.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + scopes = ((if (map.containsKey("scopes")) map["scopes"] else listOf()) 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 index 0349ffb..b7d7092 100644 --- a/library/src/main/java/io/appwrite/models/AppSecret.kt +++ b/library/src/main/java/io/appwrite/models/AppSecret.kt @@ -79,14 +79,14 @@ data class AppSecret( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + appId = (if (map.containsKey("appId")) map["appId"] else "") as String, + secret = (if (map.containsKey("secret")) map["secret"] else "") as String, + hint = (if (map.containsKey("hint")) map["hint"] else "") as String, + createdById = (if (map.containsKey("createdById")) map["createdById"] else "") as String, + createdByName = (if (map.containsKey("createdByName")) map["createdByName"] else "") 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 index 04d2ebb..1f2946c 100644 --- a/library/src/main/java/io/appwrite/models/AppSecretList.kt +++ b/library/src/main/java/io/appwrite/models/AppSecretList.kt @@ -30,8 +30,8 @@ data class AppSecretList( fun from( map: Map, ) = AppSecretList( - total = (map["total"] as Number).toLong(), - secrets = (map["secrets"] as List>).map { AppSecret.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + secrets = ((if (map.containsKey("secrets")) map["secrets"] else listOf()) 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 index 098b7f1..7464598 100644 --- a/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt +++ b/library/src/main/java/io/appwrite/models/AppSecretPlaintext.kt @@ -79,14 +79,14 @@ data class AppSecretPlaintext( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + appId = (if (map.containsKey("appId")) map["appId"] else "") as String, + secret = (if (map.containsKey("secret")) map["secret"] else "") as String, + hint = (if (map.containsKey("hint")) map["hint"] else "") as String, + createdById = (if (map.containsKey("createdById")) map["createdById"] else "") as String, + createdByName = (if (map.containsKey("createdByName")) map["createdByName"] else "") 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 index 5dcff82..0ad546f 100644 --- a/library/src/main/java/io/appwrite/models/AppsList.kt +++ b/library/src/main/java/io/appwrite/models/AppsList.kt @@ -30,8 +30,8 @@ data class AppsList( fun from( map: Map, ) = AppsList( - total = (map["total"] as Number).toLong(), - apps = (map["apps"] as List>).map { App.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + apps = ((if (map.containsKey("apps")) map["apps"] else listOf()) as List>).map { App.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 0adbb5f..e8599a0 100644 --- a/library/src/main/java/io/appwrite/models/Continent.kt +++ b/library/src/main/java/io/appwrite/models/Continent.kt @@ -30,8 +30,8 @@ data class Continent( fun from( map: Map, ) = Continent( - name = map["name"] as String, - code = map["code"] as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + code = (if (map.containsKey("code")) map["code"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/ContinentList.kt b/library/src/main/java/io/appwrite/models/ContinentList.kt index 53f95f2..0a1f4f3 100644 --- a/library/src/main/java/io/appwrite/models/ContinentList.kt +++ b/library/src/main/java/io/appwrite/models/ContinentList.kt @@ -30,8 +30,8 @@ data class ContinentList( fun from( map: Map, ) = ContinentList( - total = (map["total"] as Number).toLong(), - continents = (map["continents"] as List>).map { Continent.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + continents = ((if (map.containsKey("continents")) map["continents"] else listOf()) as List>).map { Continent.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Country.kt b/library/src/main/java/io/appwrite/models/Country.kt index 7b104f0..1c3065e 100644 --- a/library/src/main/java/io/appwrite/models/Country.kt +++ b/library/src/main/java/io/appwrite/models/Country.kt @@ -30,8 +30,8 @@ data class Country( fun from( map: Map, ) = Country( - name = map["name"] as String, - code = map["code"] as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + code = (if (map.containsKey("code")) map["code"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/CountryList.kt b/library/src/main/java/io/appwrite/models/CountryList.kt index 322cdaf..629bae2 100644 --- a/library/src/main/java/io/appwrite/models/CountryList.kt +++ b/library/src/main/java/io/appwrite/models/CountryList.kt @@ -30,8 +30,8 @@ data class CountryList( fun from( map: Map, ) = CountryList( - total = (map["total"] as Number).toLong(), - countries = (map["countries"] as List>).map { Country.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + countries = ((if (map.containsKey("countries")) map["countries"] else listOf()) as List>).map { Country.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Currency.kt b/library/src/main/java/io/appwrite/models/Currency.kt index 95ff13a..d78a90c 100644 --- a/library/src/main/java/io/appwrite/models/Currency.kt +++ b/library/src/main/java/io/appwrite/models/Currency.kt @@ -65,13 +65,13 @@ data class Currency( fun from( map: Map, ) = Currency( - symbol = map["symbol"] as String, - name = map["name"] as String, - symbolNative = map["symbolNative"] as String, - decimalDigits = (map["decimalDigits"] as Number).toLong(), - rounding = (map["rounding"] as Number).toDouble(), - code = map["code"] as String, - namePlural = map["namePlural"] as String, + symbol = (if (map.containsKey("symbol")) map["symbol"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + symbolNative = (if (map.containsKey("symbolNative")) map["symbolNative"] else "") as String, + decimalDigits = ((if (map.containsKey("decimalDigits")) map["decimalDigits"] else 0) as Number).toLong(), + rounding = ((if (map.containsKey("rounding")) map["rounding"] else 0.0) as Number).toDouble(), + code = (if (map.containsKey("code")) map["code"] else "") as String, + namePlural = (if (map.containsKey("namePlural")) map["namePlural"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/CurrencyList.kt b/library/src/main/java/io/appwrite/models/CurrencyList.kt index 8082a2f..5928c9a 100644 --- a/library/src/main/java/io/appwrite/models/CurrencyList.kt +++ b/library/src/main/java/io/appwrite/models/CurrencyList.kt @@ -30,8 +30,8 @@ data class CurrencyList( fun from( map: Map, ) = CurrencyList( - total = (map["total"] as Number).toLong(), - currencies = (map["currencies"] as List>).map { Currency.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + currencies = ((if (map.containsKey("currencies")) map["currencies"] else listOf()) as List>).map { Currency.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Document.kt b/library/src/main/java/io/appwrite/models/Document.kt index 20e803e..a19dd7f 100644 --- a/library/src/main/java/io/appwrite/models/Document.kt +++ b/library/src/main/java/io/appwrite/models/Document.kt @@ -92,13 +92,13 @@ data class Document( map: Map, nestedType: Class ) = Document( - id = map["\$id"] as String, - sequence = map["\$sequence"] as String, - collectionId = map["\$collectionId"] as String, - databaseId = map["\$databaseId"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - permissions = map["\$permissions"] as List, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + sequence = (if (map.containsKey("\$sequence")) map["\$sequence"] else "") as String, + collectionId = (if (map.containsKey("\$collectionId")) map["\$collectionId"] else "") as String, + databaseId = (if (map.containsKey("\$databaseId")) map["\$databaseId"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + permissions = (if (map.containsKey("\$permissions")) map["\$permissions"] else listOf()) as List, data = map["data"]?.jsonCast(to = nestedType) ?: map.jsonCast(to = nestedType) ) } diff --git a/library/src/main/java/io/appwrite/models/DocumentList.kt b/library/src/main/java/io/appwrite/models/DocumentList.kt index d8741ea..5920316 100644 --- a/library/src/main/java/io/appwrite/models/DocumentList.kt +++ b/library/src/main/java/io/appwrite/models/DocumentList.kt @@ -39,8 +39,8 @@ data class DocumentList( map: Map, nestedType: Class ) = DocumentList( - total = (map["total"] as Number).toLong(), - documents = (map["documents"] as List>).map { Document.from(map = it, nestedType) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + documents = ((if (map.containsKey("documents")) map["documents"] else listOf()) as List>).map { Document.from(map = it, nestedType) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Execution.kt b/library/src/main/java/io/appwrite/models/Execution.kt index c698d9c..8b36414 100644 --- a/library/src/main/java/io/appwrite/models/Execution.kt +++ b/library/src/main/java/io/appwrite/models/Execution.kt @@ -53,7 +53,7 @@ data class Execution( val deploymentId: String, /** - * The trigger that caused the resource to execute. Possible values can be: `http`, `schedule`, or `event`. + * The trigger that caused the resource to execute. Possible values can be: `http`, `schedule`, `event`, or `email`. */ @SerializedName("trigger") val trigger: ExecutionTrigger, @@ -152,24 +152,24 @@ data class Execution( fun from( map: Map, ) = Execution( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - permissions = map["\$permissions"] as List, - resourceId = map["resourceId"] as String, - resourceType = ExecutionResourceType.values().find { it.value == map["resourceType"] as String }!!, - deploymentId = map["deploymentId"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + permissions = (if (map.containsKey("\$permissions")) map["\$permissions"] else listOf()) as List, + resourceId = (if (map.containsKey("resourceId")) map["resourceId"] else "") as String, + resourceType = ExecutionResourceType.values().find { it.value == (if (map.containsKey("resourceType")) map["resourceType"] else "functions") as String }!!, + deploymentId = (if (map.containsKey("deploymentId")) map["deploymentId"] else "") as String, trigger = ExecutionTrigger.values().find { it.value == map["trigger"] as String }!!, status = ExecutionStatus.values().find { it.value == map["status"] as String }!!, - requestMethod = map["requestMethod"] as String, - requestPath = map["requestPath"] as String, - requestHeaders = (map["requestHeaders"] as List>).map { Headers.from(map = it) }, - responseStatusCode = (map["responseStatusCode"] as Number).toLong(), - responseBody = map["responseBody"] as String, - responseHeaders = (map["responseHeaders"] as List>).map { Headers.from(map = it) }, - logs = map["logs"] as String, - errors = map["errors"] as String, - duration = (map["duration"] as Number).toDouble(), + requestMethod = (if (map.containsKey("requestMethod")) map["requestMethod"] else "") as String, + requestPath = (if (map.containsKey("requestPath")) map["requestPath"] else "") as String, + requestHeaders = ((if (map.containsKey("requestHeaders")) map["requestHeaders"] else listOf()) as List>).map { Headers.from(map = it) }, + responseStatusCode = ((if (map.containsKey("responseStatusCode")) map["responseStatusCode"] else 0) as Number).toLong(), + responseBody = (if (map.containsKey("responseBody")) map["responseBody"] else "") as String, + responseHeaders = ((if (map.containsKey("responseHeaders")) map["responseHeaders"] else listOf()) as List>).map { Headers.from(map = it) }, + logs = (if (map.containsKey("logs")) map["logs"] else "") as String, + errors = (if (map.containsKey("errors")) map["errors"] else "") as String, + duration = ((if (map.containsKey("duration")) map["duration"] else 0.0) as Number).toDouble(), scheduledAt = map["scheduledAt"] 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 4eac1d8..0eba281 100644 --- a/library/src/main/java/io/appwrite/models/ExecutionList.kt +++ b/library/src/main/java/io/appwrite/models/ExecutionList.kt @@ -30,8 +30,8 @@ data class ExecutionList( fun from( map: Map, ) = ExecutionList( - total = (map["total"] as Number).toLong(), - executions = (map["executions"] as List>).map { Execution.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + executions = ((if (map.containsKey("executions")) map["executions"] else listOf()) as List>).map { Execution.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/File.kt b/library/src/main/java/io/appwrite/models/File.kt index d15a55b..049bbae 100644 --- a/library/src/main/java/io/appwrite/models/File.kt +++ b/library/src/main/java/io/appwrite/models/File.kt @@ -128,22 +128,22 @@ data class File( fun from( map: Map, ) = File( - id = map["\$id"] as String, - bucketId = map["bucketId"] as String, - createdAt = map["\$createdAt"] as String, - 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(), - sizeActual = (map["sizeActual"] as Number).toLong(), - chunksTotal = (map["chunksTotal"] as Number).toLong(), - chunksUploaded = (map["chunksUploaded"] as Number).toLong(), - encryption = map["encryption"] as Boolean, - compression = map["compression"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + bucketId = (if (map.containsKey("bucketId")) map["bucketId"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + permissions = (if (map.containsKey("\$permissions")) map["\$permissions"] else listOf()) as List, + name = (if (map.containsKey("name")) map["name"] else "") as String, + folder = (if (map.containsKey("folder")) map["folder"] else "") as String, + key = (if (map.containsKey("key")) map["key"] else "") as String, + signature = (if (map.containsKey("signature")) map["signature"] else "") as String, + mimeType = (if (map.containsKey("mimeType")) map["mimeType"] else "") as String, + sizeOriginal = ((if (map.containsKey("sizeOriginal")) map["sizeOriginal"] else 0) as Number).toLong(), + sizeActual = ((if (map.containsKey("sizeActual")) map["sizeActual"] else 0) as Number).toLong(), + chunksTotal = ((if (map.containsKey("chunksTotal")) map["chunksTotal"] else 0) as Number).toLong(), + chunksUploaded = ((if (map.containsKey("chunksUploaded")) map["chunksUploaded"] else 0) as Number).toLong(), + encryption = (if (map.containsKey("encryption")) map["encryption"] else false) as Boolean, + compression = (if (map.containsKey("compression")) map["compression"] else "none") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/FileList.kt b/library/src/main/java/io/appwrite/models/FileList.kt index 7a462dc..1a84a46 100644 --- a/library/src/main/java/io/appwrite/models/FileList.kt +++ b/library/src/main/java/io/appwrite/models/FileList.kt @@ -30,8 +30,8 @@ data class FileList( fun from( map: Map, ) = FileList( - total = (map["total"] as Number).toLong(), - files = (map["files"] as List>).map { File.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + files = ((if (map.containsKey("files")) map["files"] else listOf()) as List>).map { File.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Headers.kt b/library/src/main/java/io/appwrite/models/Headers.kt index 4fece1e..d2c2777 100644 --- a/library/src/main/java/io/appwrite/models/Headers.kt +++ b/library/src/main/java/io/appwrite/models/Headers.kt @@ -30,8 +30,8 @@ data class Headers( fun from( map: Map, ) = Headers( - name = map["name"] as String, - value = map["value"] as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + value = (if (map.containsKey("value")) map["value"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Identity.kt b/library/src/main/java/io/appwrite/models/Identity.kt index 011c32e..0269400 100644 --- a/library/src/main/java/io/appwrite/models/Identity.kt +++ b/library/src/main/java/io/appwrite/models/Identity.kt @@ -93,17 +93,17 @@ data class Identity( fun from( map: Map, ) = Identity( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - userId = map["userId"] as String, - provider = map["provider"] as String, - providerUid = map["providerUid"] as String, - providerEmail = map["providerEmail"] as String, - providerAccessToken = map["providerAccessToken"] as String, - providerAccessTokenExpiry = map["providerAccessTokenExpiry"] as String, - providerRefreshToken = map["providerRefreshToken"] as String, - providerIdToken = map["providerIdToken"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + provider = (if (map.containsKey("provider")) map["provider"] else "") as String, + providerUid = (if (map.containsKey("providerUid")) map["providerUid"] else "") as String, + providerEmail = (if (map.containsKey("providerEmail")) map["providerEmail"] else "") as String, + providerAccessToken = (if (map.containsKey("providerAccessToken")) map["providerAccessToken"] else "") as String, + providerAccessTokenExpiry = (if (map.containsKey("providerAccessTokenExpiry")) map["providerAccessTokenExpiry"] else "") as String, + providerRefreshToken = (if (map.containsKey("providerRefreshToken")) map["providerRefreshToken"] else "") as String, + providerIdToken = (if (map.containsKey("providerIdToken")) map["providerIdToken"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/IdentityList.kt b/library/src/main/java/io/appwrite/models/IdentityList.kt index b92f4e6..31e5b0e 100644 --- a/library/src/main/java/io/appwrite/models/IdentityList.kt +++ b/library/src/main/java/io/appwrite/models/IdentityList.kt @@ -30,8 +30,8 @@ data class IdentityList( fun from( map: Map, ) = IdentityList( - total = (map["total"] as Number).toLong(), - identities = (map["identities"] as List>).map { Identity.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + identities = ((if (map.containsKey("identities")) map["identities"] else listOf()) as List>).map { Identity.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Jwt.kt b/library/src/main/java/io/appwrite/models/Jwt.kt index c188a85..7b27865 100644 --- a/library/src/main/java/io/appwrite/models/Jwt.kt +++ b/library/src/main/java/io/appwrite/models/Jwt.kt @@ -23,7 +23,7 @@ data class Jwt( fun from( map: Map, ) = Jwt( - jwt = map["jwt"] as String, + jwt = (if (map.containsKey("jwt")) map["jwt"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Language.kt b/library/src/main/java/io/appwrite/models/Language.kt index be1f30e..01eb45b 100644 --- a/library/src/main/java/io/appwrite/models/Language.kt +++ b/library/src/main/java/io/appwrite/models/Language.kt @@ -37,9 +37,9 @@ data class Language( fun from( map: Map, ) = Language( - name = map["name"] as String, - code = map["code"] as String, - nativeName = map["nativeName"] as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + code = (if (map.containsKey("code")) map["code"] else "") as String, + nativeName = (if (map.containsKey("nativeName")) map["nativeName"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/LanguageList.kt b/library/src/main/java/io/appwrite/models/LanguageList.kt index e1b0e6e..86f6aed 100644 --- a/library/src/main/java/io/appwrite/models/LanguageList.kt +++ b/library/src/main/java/io/appwrite/models/LanguageList.kt @@ -30,8 +30,8 @@ data class LanguageList( fun from( map: Map, ) = LanguageList( - total = (map["total"] as Number).toLong(), - languages = (map["languages"] as List>).map { Language.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + languages = ((if (map.containsKey("languages")) map["languages"] else listOf()) as List>).map { Language.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Locale.kt b/library/src/main/java/io/appwrite/models/Locale.kt index 689de89..dc6a1ad 100644 --- a/library/src/main/java/io/appwrite/models/Locale.kt +++ b/library/src/main/java/io/appwrite/models/Locale.kt @@ -142,13 +142,13 @@ data class Locale( fun from( map: Map, ) = Locale( - ip = map["ip"] as String, - countryCode = map["countryCode"] as String, - country = map["country"] as String, - continentCode = map["continentCode"] as String, - continent = map["continent"] as String, - eu = map["eu"] as Boolean, - currency = map["currency"] as String, + ip = (if (map.containsKey("ip")) map["ip"] else "") as String, + countryCode = (if (map.containsKey("countryCode")) map["countryCode"] else "") as String, + country = (if (map.containsKey("country")) map["country"] else "") as String, + continentCode = (if (map.containsKey("continentCode")) map["continentCode"] else "") as String, + continent = (if (map.containsKey("continent")) map["continent"] else "") as String, + eu = (if (map.containsKey("eu")) map["eu"] else false) as Boolean, + currency = (if (map.containsKey("currency")) map["currency"] else "") as String, city = map["city"] as? String, timeZone = map["timeZone"] as? String, postalCode = map["postalCode"] as? String, diff --git a/library/src/main/java/io/appwrite/models/LocaleCode.kt b/library/src/main/java/io/appwrite/models/LocaleCode.kt index 602d9ed..3bee528 100644 --- a/library/src/main/java/io/appwrite/models/LocaleCode.kt +++ b/library/src/main/java/io/appwrite/models/LocaleCode.kt @@ -30,8 +30,8 @@ data class LocaleCode( fun from( map: Map, ) = LocaleCode( - code = map["code"] as String, - name = map["name"] as String, + code = (if (map.containsKey("code")) map["code"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/LocaleCodeList.kt b/library/src/main/java/io/appwrite/models/LocaleCodeList.kt index e0c568a..86cb808 100644 --- a/library/src/main/java/io/appwrite/models/LocaleCodeList.kt +++ b/library/src/main/java/io/appwrite/models/LocaleCodeList.kt @@ -30,8 +30,8 @@ data class LocaleCodeList( fun from( map: Map, ) = LocaleCodeList( - total = (map["total"] as Number).toLong(), - localeCodes = (map["localeCodes"] as List>).map { LocaleCode.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + localeCodes = ((if (map.containsKey("localeCodes")) map["localeCodes"] else listOf()) as List>).map { LocaleCode.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Membership.kt b/library/src/main/java/io/appwrite/models/Membership.kt index 7d5b963..3d0c37b 100644 --- a/library/src/main/java/io/appwrite/models/Membership.kt +++ b/library/src/main/java/io/appwrite/models/Membership.kt @@ -121,21 +121,21 @@ data class Membership( fun from( map: Map, ) = Membership( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - userId = map["userId"] as String, - userName = map["userName"] as String, - userEmail = map["userEmail"] as String, - userPhone = map["userPhone"] as String, - teamId = map["teamId"] as String, - teamName = map["teamName"] as String, - invited = map["invited"] as String, - joined = map["joined"] as String, - confirm = map["confirm"] as Boolean, - mfa = map["mfa"] as Boolean, - userAccessedAt = map["userAccessedAt"] as String, - roles = map["roles"] as List, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + userName = (if (map.containsKey("userName")) map["userName"] else "") as String, + userEmail = (if (map.containsKey("userEmail")) map["userEmail"] else "") as String, + userPhone = (if (map.containsKey("userPhone")) map["userPhone"] else "") as String, + teamId = (if (map.containsKey("teamId")) map["teamId"] else "") as String, + teamName = (if (map.containsKey("teamName")) map["teamName"] else "") as String, + invited = (if (map.containsKey("invited")) map["invited"] else "") as String, + joined = (if (map.containsKey("joined")) map["joined"] else "") as String, + confirm = (if (map.containsKey("confirm")) map["confirm"] else false) as Boolean, + mfa = (if (map.containsKey("mfa")) map["mfa"] else false) as Boolean, + userAccessedAt = (if (map.containsKey("userAccessedAt")) map["userAccessedAt"] else "") as String, + roles = (if (map.containsKey("roles")) map["roles"] else listOf()) as List, ) } } diff --git a/library/src/main/java/io/appwrite/models/MembershipList.kt b/library/src/main/java/io/appwrite/models/MembershipList.kt index 8376bc9..f6f4afe 100644 --- a/library/src/main/java/io/appwrite/models/MembershipList.kt +++ b/library/src/main/java/io/appwrite/models/MembershipList.kt @@ -30,8 +30,8 @@ data class MembershipList( fun from( map: Map, ) = MembershipList( - total = (map["total"] as Number).toLong(), - memberships = (map["memberships"] as List>).map { Membership.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + memberships = ((if (map.containsKey("memberships")) map["memberships"] else listOf()) as List>).map { Membership.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/MfaChallenge.kt b/library/src/main/java/io/appwrite/models/MfaChallenge.kt index b3e9f6b..c17e792 100644 --- a/library/src/main/java/io/appwrite/models/MfaChallenge.kt +++ b/library/src/main/java/io/appwrite/models/MfaChallenge.kt @@ -44,10 +44,10 @@ data class MfaChallenge( fun from( map: Map, ) = MfaChallenge( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - userId = map["userId"] as String, - expire = map["expire"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/MfaFactors.kt b/library/src/main/java/io/appwrite/models/MfaFactors.kt index 20f7bb3..42872fa 100644 --- a/library/src/main/java/io/appwrite/models/MfaFactors.kt +++ b/library/src/main/java/io/appwrite/models/MfaFactors.kt @@ -51,11 +51,11 @@ data class MfaFactors( fun from( map: Map, ) = MfaFactors( - totp = map["totp"] as Boolean, - phone = map["phone"] as Boolean, - email = map["email"] as Boolean, - recoveryCode = map["recoveryCode"] as Boolean, - custom = map["custom"] as Boolean, + totp = (if (map.containsKey("totp")) map["totp"] else false) as Boolean, + phone = (if (map.containsKey("phone")) map["phone"] else false) as Boolean, + email = (if (map.containsKey("email")) map["email"] else false) as Boolean, + recoveryCode = (if (map.containsKey("recoveryCode")) map["recoveryCode"] else false) as Boolean, + custom = (if (map.containsKey("custom")) map["custom"] else false) 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 a896749..25e783c 100644 --- a/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt +++ b/library/src/main/java/io/appwrite/models/MfaRecoveryCodes.kt @@ -23,7 +23,7 @@ data class MfaRecoveryCodes( fun from( map: Map, ) = MfaRecoveryCodes( - recoveryCodes = map["recoveryCodes"] as List, + recoveryCodes = (if (map.containsKey("recoveryCodes")) map["recoveryCodes"] else listOf()) as List, ) } } diff --git a/library/src/main/java/io/appwrite/models/MfaType.kt b/library/src/main/java/io/appwrite/models/MfaType.kt index e20b7f6..5b7b971 100644 --- a/library/src/main/java/io/appwrite/models/MfaType.kt +++ b/library/src/main/java/io/appwrite/models/MfaType.kt @@ -30,8 +30,8 @@ data class MfaType( fun from( map: Map, ) = MfaType( - secret = map["secret"] as String, - uri = map["uri"] as String, + secret = (if (map.containsKey("secret")) map["secret"] else "") as String, + uri = (if (map.containsKey("uri")) map["uri"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2Approve.kt b/library/src/main/java/io/appwrite/models/Oauth2Approve.kt index e09ab9e..0aabf4d 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Approve.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Approve.kt @@ -23,7 +23,7 @@ data class Oauth2Approve( fun from( map: Map, ) = Oauth2Approve( - redirectUrl = map["redirectUrl"] as String, + redirectUrl = (if (map.containsKey("redirectUrl")) map["redirectUrl"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt b/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt index e0812f7..fe878e3 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Authorize.kt @@ -30,8 +30,8 @@ data class Oauth2Authorize( fun from( map: Map, ) = Oauth2Authorize( - grantId = map["grantId"] as String, - redirectUrl = map["redirectUrl"] as String, + grantId = (if (map.containsKey("grantId")) map["grantId"] else "") as String, + redirectUrl = (if (map.containsKey("redirectUrl")) map["redirectUrl"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2Consent.kt b/library/src/main/java/io/appwrite/models/Oauth2Consent.kt index 7769e9b..28cde91 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Consent.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Consent.kt @@ -86,16 +86,16 @@ data class Oauth2Consent( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + appId = (if (map.containsKey("appId")) map["appId"] else "") as String, + cimdUrl = (if (map.containsKey("cimdUrl")) map["cimdUrl"] else "") as String, + scopes = (if (map.containsKey("scopes")) map["scopes"] else listOf()) as List, + resources = (if (map.containsKey("resources")) map["resources"] else listOf()) as List, + authorizationDetails = (if (map.containsKey("authorizationDetails")) map["authorizationDetails"] else "[]") as String, + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt b/library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt index da6acd3..2a6337b 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2ConsentList.kt @@ -30,8 +30,8 @@ data class Oauth2ConsentList( fun from( map: Map, ) = Oauth2ConsentList( - total = (map["total"] as Number).toLong(), - consents = (map["consents"] as List>).map { Oauth2Consent.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + consents = ((if (map.containsKey("consents")) map["consents"] else listOf()) 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 index 31f6249..1f5c1df 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2ConsentToken.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2ConsentToken.kt @@ -93,17 +93,17 @@ data class Oauth2ConsentToken( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + consentId = (if (map.containsKey("consentId")) map["consentId"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + appId = (if (map.containsKey("appId")) map["appId"] else "") as String, + cimdUrl = (if (map.containsKey("cimdUrl")) map["cimdUrl"] else "") as String, + scopes = (if (map.containsKey("scopes")) map["scopes"] else listOf()) as List, + resources = (if (map.containsKey("resources")) map["resources"] else listOf()) as List, + authorizationDetails = (if (map.containsKey("authorizationDetails")) map["authorizationDetails"] else "[]") as String, + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt b/library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt index 09278f4..b5d4c52 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2ConsentTokenList.kt @@ -30,8 +30,8 @@ data class Oauth2ConsentTokenList( fun from( map: Map, ) = Oauth2ConsentTokenList( - total = (map["total"] as Number).toLong(), - tokens = (map["tokens"] as List>).map { Oauth2ConsentToken.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + tokens = ((if (map.containsKey("tokens")) map["tokens"] else listOf()) as List>).map { Oauth2ConsentToken.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt b/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt index 58c7e5e..f4ca6e6 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2DeviceAuthorization.kt @@ -58,12 +58,12 @@ data class Oauth2DeviceAuthorization( 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(), + device_code = (if (map.containsKey("device_code")) map["device_code"] else "") as String, + user_code = (if (map.containsKey("user_code")) map["user_code"] else "") as String, + verification_uri = (if (map.containsKey("verification_uri")) map["verification_uri"] else "") as String, + verification_uri_complete = (if (map.containsKey("verification_uri_complete")) map["verification_uri_complete"] else "") as String, + expires_in = ((if (map.containsKey("expires_in")) map["expires_in"] else 0) as Number).toLong(), + interval = ((if (map.containsKey("interval")) map["interval"] else 0) 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 index ef1a7c3..914b6f3 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Grant.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Grant.kt @@ -100,18 +100,18 @@ data class Oauth2Grant( 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, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + appId = (if (map.containsKey("appId")) map["appId"] else "") as String, + scopes = (if (map.containsKey("scopes")) map["scopes"] else listOf()) as List, + resources = (if (map.containsKey("resources")) map["resources"] else listOf()) as List, + authorizationDetails = (if (map.containsKey("authorizationDetails")) map["authorizationDetails"] else "[]") as String, + prompt = (if (map.containsKey("prompt")) map["prompt"] else "") as String, + redirectUri = (if (map.containsKey("redirectUri")) map["redirectUri"] else "") as String, + authTime = ((if (map.containsKey("authTime")) map["authTime"] else 0) as Number).toLong(), + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2Organization.kt b/library/src/main/java/io/appwrite/models/Oauth2Organization.kt index 605916a..47b6a8f 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Organization.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Organization.kt @@ -23,7 +23,7 @@ data class Oauth2Organization( fun from( map: Map, ) = Oauth2Organization( - id = map["\$id"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt b/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt index b90ee63..48f4506 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2OrganizationList.kt @@ -30,8 +30,8 @@ data class Oauth2OrganizationList( fun from( map: Map, ) = Oauth2OrganizationList( - total = (map["total"] as Number).toLong(), - organizations = (map["organizations"] as List>).map { Oauth2Organization.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + organizations = ((if (map.containsKey("organizations")) map["organizations"] else listOf()) 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 index 94d175e..1091bdd 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2PAR.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2PAR.kt @@ -30,8 +30,8 @@ data class Oauth2PAR( fun from( map: Map, ) = Oauth2PAR( - request_uri = map["request_uri"] as String, - expires_in = (map["expires_in"] as Number).toLong(), + request_uri = (if (map.containsKey("request_uri")) map["request_uri"] else "") as String, + expires_in = ((if (map.containsKey("expires_in")) map["expires_in"] else 0) 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 index eb085dc..95c79e0 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Project.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Project.kt @@ -37,9 +37,9 @@ data class Oauth2Project( fun from( map: Map, ) = Oauth2Project( - id = map["\$id"] as String, - region = map["region"] as String, - endpoint = map["endpoint"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + region = (if (map.containsKey("region")) map["region"] else "") as String, + endpoint = (if (map.containsKey("endpoint")) map["endpoint"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt b/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt index 551b9ee..5eb46ed 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2ProjectList.kt @@ -30,8 +30,8 @@ data class Oauth2ProjectList( fun from( map: Map, ) = Oauth2ProjectList( - total = (map["total"] as Number).toLong(), - projects = (map["projects"] as List>).map { Oauth2Project.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + projects = ((if (map.containsKey("projects")) map["projects"] else listOf()) 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 index b23f308..42b3a3a 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Reject.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Reject.kt @@ -23,7 +23,7 @@ data class Oauth2Reject( fun from( map: Map, ) = Oauth2Reject( - redirectUrl = map["redirectUrl"] as String, + redirectUrl = (if (map.containsKey("redirectUrl")) map["redirectUrl"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Oauth2Token.kt b/library/src/main/java/io/appwrite/models/Oauth2Token.kt index f36f776..53547c2 100644 --- a/library/src/main/java/io/appwrite/models/Oauth2Token.kt +++ b/library/src/main/java/io/appwrite/models/Oauth2Token.kt @@ -65,11 +65,11 @@ data class Oauth2Token( 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, + access_token = (if (map.containsKey("access_token")) map["access_token"] else "") as String, + token_type = (if (map.containsKey("token_type")) map["token_type"] else "Bearer") as String, + expires_in = ((if (map.containsKey("expires_in")) map["expires_in"] else 0) as Number).toLong(), + refresh_token = (if (map.containsKey("refresh_token")) map["refresh_token"] else "") as String, + scope = (if (map.containsKey("scope")) map["scope"] else "") 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/Passkey.kt b/library/src/main/java/io/appwrite/models/Passkey.kt new file mode 100644 index 0000000..90f0e49 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/Passkey.kt @@ -0,0 +1,61 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Passkey + */ +data class Passkey( + /** + * Passkey ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Passkey creation date in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * Passkey update date in ISO 8601 format. + */ + @SerializedName("\$updatedAt") + val updatedAt: String, + + /** + * Passkey name. + */ + @SerializedName("name") + val name: String, + + /** + * Most recent sign-in with this passkey in ISO 8601 format. Empty until the passkey is used to sign in. + */ + @SerializedName("accessedAt") + val accessedAt: 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, + "accessedAt" to accessedAt as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = Passkey( + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + accessedAt = (if (map.containsKey("accessedAt")) map["accessedAt"] else "") as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/PasskeyChallenge.kt b/library/src/main/java/io/appwrite/models/PasskeyChallenge.kt new file mode 100644 index 0000000..dcdcb77 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/PasskeyChallenge.kt @@ -0,0 +1,61 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Passkey Challenge + */ +data class PasskeyChallenge( + /** + * Challenge ID. When signing in, pass it back with the credential to receive a token. + */ + @SerializedName("\$id") + val id: String, + + /** + * Challenge creation date in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * ID of the passkey being registered. Empty for sign-in challenges. + */ + @SerializedName("passkeyId") + val passkeyId: String, + + /** + * Challenge expiration date in ISO 8601 format. + */ + @SerializedName("expire") + val expire: String, + + /** + * WebAuthn options in JSON form. Pass them to `PublicKeyCredential.parseCreationOptionsFromJSON()` when registering, or `PublicKeyCredential.parseRequestOptionsFromJSON()` when signing in. + */ + @SerializedName("publicKey") + val publicKey: Map, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "passkeyId" to passkeyId as Any, + "expire" to expire as Any, + "publicKey" to publicKey as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = PasskeyChallenge( + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + passkeyId = (if (map.containsKey("passkeyId")) map["passkeyId"] else "") as String, + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, + publicKey = (if (map.containsKey("publicKey")) map["publicKey"] else mapOf()) as Map, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/PasskeyList.kt b/library/src/main/java/io/appwrite/models/PasskeyList.kt new file mode 100644 index 0000000..0569229 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/PasskeyList.kt @@ -0,0 +1,37 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Passkeys List + */ +data class PasskeyList( + /** + * Total number of passkeys that matched your query. + */ + @SerializedName("total") + val total: Long, + + /** + * List of passkeys. + */ + @SerializedName("passkeys") + val passkeys: List, + +) { + fun toMap(): Map = mapOf( + "total" to total as Any, + "passkeys" to passkeys.map { it.toMap() } as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = PasskeyList( + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + passkeys = ((if (map.containsKey("passkeys")) map["passkeys"] else listOf()) as List>).map { Passkey.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 74bfa51..12c8eb6 100644 --- a/library/src/main/java/io/appwrite/models/Phone.kt +++ b/library/src/main/java/io/appwrite/models/Phone.kt @@ -37,9 +37,9 @@ data class Phone( fun from( map: Map, ) = Phone( - code = map["code"] as String, - countryCode = map["countryCode"] as String, - countryName = map["countryName"] as String, + code = (if (map.containsKey("code")) map["code"] else "") as String, + countryCode = (if (map.containsKey("countryCode")) map["countryCode"] else "") as String, + countryName = (if (map.containsKey("countryName")) map["countryName"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/PhoneList.kt b/library/src/main/java/io/appwrite/models/PhoneList.kt index fc0ffa3..2c3f9cd 100644 --- a/library/src/main/java/io/appwrite/models/PhoneList.kt +++ b/library/src/main/java/io/appwrite/models/PhoneList.kt @@ -30,8 +30,8 @@ data class PhoneList( fun from( map: Map, ) = PhoneList( - total = (map["total"] as Number).toLong(), - phones = (map["phones"] as List>).map { Phone.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + phones = ((if (map.containsKey("phones")) map["phones"] else listOf()) as List>).map { Phone.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Presence.kt b/library/src/main/java/io/appwrite/models/Presence.kt index c7f6073..b8e27fc 100644 --- a/library/src/main/java/io/appwrite/models/Presence.kt +++ b/library/src/main/java/io/appwrite/models/Presence.kt @@ -79,13 +79,13 @@ data class Presence( fun from( map: Map, ) = Presence( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - permissions = map["\$permissions"] as List, - userId = map["userId"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + permissions = (if (map.containsKey("\$permissions")) map["\$permissions"] else listOf()) as List, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, status = map["status"] as? String, - source = map["source"] as String, + source = (if (map.containsKey("source")) map["source"] else "") as String, expiresAt = map["expiresAt"] as? String, 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 b69d3e1..01a2026 100644 --- a/library/src/main/java/io/appwrite/models/PresenceList.kt +++ b/library/src/main/java/io/appwrite/models/PresenceList.kt @@ -30,8 +30,8 @@ data class PresenceList( fun from( map: Map, ) = PresenceList( - total = (map["total"] as Number).toLong(), - presences = (map["presences"] as List>).map { Presence.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + presences = ((if (map.containsKey("presences")) map["presences"] else listOf()) as List>).map { Presence.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Row.kt b/library/src/main/java/io/appwrite/models/Row.kt index 1f19010..d096846 100644 --- a/library/src/main/java/io/appwrite/models/Row.kt +++ b/library/src/main/java/io/appwrite/models/Row.kt @@ -92,13 +92,13 @@ data class Row( map: Map, nestedType: Class ) = Row( - id = map["\$id"] as String, - sequence = map["\$sequence"] as String, - tableId = map["\$tableId"] as String, - databaseId = map["\$databaseId"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - permissions = map["\$permissions"] as List, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + sequence = (if (map.containsKey("\$sequence")) map["\$sequence"] else "") as String, + tableId = (if (map.containsKey("\$tableId")) map["\$tableId"] else "") as String, + databaseId = (if (map.containsKey("\$databaseId")) map["\$databaseId"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + permissions = (if (map.containsKey("\$permissions")) map["\$permissions"] else listOf()) as List, data = map["data"]?.jsonCast(to = nestedType) ?: map.jsonCast(to = nestedType) ) } diff --git a/library/src/main/java/io/appwrite/models/RowList.kt b/library/src/main/java/io/appwrite/models/RowList.kt index f9c8f61..0d52602 100644 --- a/library/src/main/java/io/appwrite/models/RowList.kt +++ b/library/src/main/java/io/appwrite/models/RowList.kt @@ -39,8 +39,8 @@ data class RowList( map: Map, nestedType: Class ) = RowList( - total = (map["total"] as Number).toLong(), - rows = (map["rows"] as List>).map { Row.from(map = it, nestedType) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + rows = ((if (map.containsKey("rows")) map["rows"] else listOf()) as List>).map { Row.from(map = it, nestedType) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Session.kt b/library/src/main/java/io/appwrite/models/Session.kt index c9c3950..884d581 100644 --- a/library/src/main/java/io/appwrite/models/Session.kt +++ b/library/src/main/java/io/appwrite/models/Session.kt @@ -219,35 +219,35 @@ data class Session( fun from( map: Map, ) = Session( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - userId = map["userId"] as String, - expire = map["expire"] as String, - provider = map["provider"] as String, - providerUid = map["providerUid"] as String, - providerAccessToken = map["providerAccessToken"] as String, - providerAccessTokenExpiry = map["providerAccessTokenExpiry"] as String, - providerRefreshToken = map["providerRefreshToken"] as String, - ip = map["ip"] 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, - current = map["current"] as Boolean, - factors = map["factors"] as List, - secret = map["secret"] as String, - mfaUpdatedAt = map["mfaUpdatedAt"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, + provider = (if (map.containsKey("provider")) map["provider"] else "") as String, + providerUid = (if (map.containsKey("providerUid")) map["providerUid"] else "") as String, + providerAccessToken = (if (map.containsKey("providerAccessToken")) map["providerAccessToken"] else "") as String, + providerAccessTokenExpiry = (if (map.containsKey("providerAccessTokenExpiry")) map["providerAccessTokenExpiry"] else "") as String, + providerRefreshToken = (if (map.containsKey("providerRefreshToken")) map["providerRefreshToken"] else "") as String, + ip = (if (map.containsKey("ip")) map["ip"] else "") as String, + osCode = (if (map.containsKey("osCode")) map["osCode"] else "") as String, + osName = (if (map.containsKey("osName")) map["osName"] else "") as String, + osVersion = (if (map.containsKey("osVersion")) map["osVersion"] else "") as String, + clientType = (if (map.containsKey("clientType")) map["clientType"] else "") as String, + clientCode = (if (map.containsKey("clientCode")) map["clientCode"] else "") as String, + clientName = (if (map.containsKey("clientName")) map["clientName"] else "") as String, + clientVersion = (if (map.containsKey("clientVersion")) map["clientVersion"] else "") as String, + clientEngine = (if (map.containsKey("clientEngine")) map["clientEngine"] else "") as String, + clientEngineVersion = (if (map.containsKey("clientEngineVersion")) map["clientEngineVersion"] else "") as String, + deviceName = (if (map.containsKey("deviceName")) map["deviceName"] else "") as String, + deviceBrand = (if (map.containsKey("deviceBrand")) map["deviceBrand"] else "") as String, + deviceModel = (if (map.containsKey("deviceModel")) map["deviceModel"] else "") as String, + countryCode = (if (map.containsKey("countryCode")) map["countryCode"] else "") as String, + countryName = (if (map.containsKey("countryName")) map["countryName"] else "") as String, + current = (if (map.containsKey("current")) map["current"] else false) as Boolean, + factors = (if (map.containsKey("factors")) map["factors"] else listOf()) as List, + secret = (if (map.containsKey("secret")) map["secret"] else "") as String, + mfaUpdatedAt = (if (map.containsKey("mfaUpdatedAt")) map["mfaUpdatedAt"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/SessionList.kt b/library/src/main/java/io/appwrite/models/SessionList.kt index bda39fc..9be215b 100644 --- a/library/src/main/java/io/appwrite/models/SessionList.kt +++ b/library/src/main/java/io/appwrite/models/SessionList.kt @@ -30,8 +30,8 @@ data class SessionList( fun from( map: Map, ) = SessionList( - total = (map["total"] as Number).toLong(), - sessions = (map["sessions"] as List>).map { Session.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + sessions = ((if (map.containsKey("sessions")) map["sessions"] else listOf()) as List>).map { Session.from(map = it) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Subscriber.kt b/library/src/main/java/io/appwrite/models/Subscriber.kt index 931b65d..adcd8c5 100644 --- a/library/src/main/java/io/appwrite/models/Subscriber.kt +++ b/library/src/main/java/io/appwrite/models/Subscriber.kt @@ -79,15 +79,15 @@ data class Subscriber( fun from( map: Map, ) = Subscriber( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - targetId = map["targetId"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + targetId = (if (map.containsKey("targetId")) map["targetId"] else "") as String, target = Target.from(map = map["target"] as Map), - userId = map["userId"] as String, - userName = map["userName"] as String, - topicId = map["topicId"] as String, - providerType = map["providerType"] as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + userName = (if (map.containsKey("userName")) map["userName"] else "") as String, + topicId = (if (map.containsKey("topicId")) map["topicId"] else "") as String, + providerType = (if (map.containsKey("providerType")) map["providerType"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/Target.kt b/library/src/main/java/io/appwrite/models/Target.kt index 3e89e08..d67a716 100644 --- a/library/src/main/java/io/appwrite/models/Target.kt +++ b/library/src/main/java/io/appwrite/models/Target.kt @@ -79,15 +79,15 @@ data class Target( fun from( map: Map, ) = Target( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - name = map["name"] as String, - userId = map["userId"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, providerId = map["providerId"] as? String, - providerType = map["providerType"] as String, - identifier = map["identifier"] as String, - expired = map["expired"] as Boolean, + providerType = (if (map.containsKey("providerType")) map["providerType"] else "") as String, + identifier = (if (map.containsKey("identifier")) map["identifier"] else "") as String, + expired = (if (map.containsKey("expired")) map["expired"] else false) as Boolean, ) } } diff --git a/library/src/main/java/io/appwrite/models/Team.kt b/library/src/main/java/io/appwrite/models/Team.kt index 6cd7dbc..dc780c8 100644 --- a/library/src/main/java/io/appwrite/models/Team.kt +++ b/library/src/main/java/io/appwrite/models/Team.kt @@ -75,11 +75,11 @@ data class Team( map: Map, nestedType: Class ) = Team( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - name = map["name"] as String, - total = (map["total"] as Number).toLong(), + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), prefs = Preferences.from(map = map["prefs"] as Map, nestedType), ) } diff --git a/library/src/main/java/io/appwrite/models/TeamList.kt b/library/src/main/java/io/appwrite/models/TeamList.kt index 8e490f5..db4e1eb 100644 --- a/library/src/main/java/io/appwrite/models/TeamList.kt +++ b/library/src/main/java/io/appwrite/models/TeamList.kt @@ -39,8 +39,8 @@ data class TeamList( map: Map, nestedType: Class ) = TeamList( - total = (map["total"] as Number).toLong(), - teams = (map["teams"] as List>).map { Team.from(map = it, nestedType) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + teams = ((if (map.containsKey("teams")) map["teams"] else listOf()) as List>).map { Team.from(map = it, nestedType) }, ) } } diff --git a/library/src/main/java/io/appwrite/models/Token.kt b/library/src/main/java/io/appwrite/models/Token.kt index 625e308..c376239 100644 --- a/library/src/main/java/io/appwrite/models/Token.kt +++ b/library/src/main/java/io/appwrite/models/Token.kt @@ -58,12 +58,12 @@ data class Token( fun from( map: Map, ) = Token( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - userId = map["userId"] as String, - secret = map["secret"] as String, - expire = map["expire"] as String, - phrase = map["phrase"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + secret = (if (map.containsKey("secret")) map["secret"] else "") as String, + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, + phrase = (if (map.containsKey("phrase")) map["phrase"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/TokenSecret.kt b/library/src/main/java/io/appwrite/models/TokenSecret.kt new file mode 100644 index 0000000..fe2dd92 --- /dev/null +++ b/library/src/main/java/io/appwrite/models/TokenSecret.kt @@ -0,0 +1,69 @@ +package io.appwrite.models + +import com.google.gson.annotations.SerializedName +import io.appwrite.extensions.jsonCast + +/** + * Token Secret + */ +data class TokenSecret( + /** + * Token ID. + */ + @SerializedName("\$id") + val id: String, + + /** + * Token creation date in ISO 8601 format. + */ + @SerializedName("\$createdAt") + val createdAt: String, + + /** + * User ID. + */ + @SerializedName("userId") + val userId: String, + + /** + * Token secret key. Exchange it for a session with the create session endpoint. + */ + @SerializedName("secret") + val secret: String, + + /** + * Token expiration date in ISO 8601 format. + */ + @SerializedName("expire") + val expire: String, + + /** + * Security phrase of a token. Empty if security phrase was not requested when creating a token. It includes randomly generated phrase which is also sent in the external resource such as email. + */ + @SerializedName("phrase") + val phrase: String, + +) { + fun toMap(): Map = mapOf( + "\$id" to id as Any, + "\$createdAt" to createdAt as Any, + "userId" to userId as Any, + "secret" to secret as Any, + "expire" to expire as Any, + "phrase" to phrase as Any, + ) + + companion object { + @Suppress("UNCHECKED_CAST") + fun from( + map: Map, + ) = TokenSecret( + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + userId = (if (map.containsKey("userId")) map["userId"] else "") as String, + secret = (if (map.containsKey("secret")) map["secret"] else "") as String, + expire = (if (map.containsKey("expire")) map["expire"] else "") as String, + phrase = (if (map.containsKey("phrase")) map["phrase"] else "") as String, + ) + } +} diff --git a/library/src/main/java/io/appwrite/models/Transaction.kt b/library/src/main/java/io/appwrite/models/Transaction.kt index 8f09860..bb5f39b 100644 --- a/library/src/main/java/io/appwrite/models/Transaction.kt +++ b/library/src/main/java/io/appwrite/models/Transaction.kt @@ -58,12 +58,12 @@ data class Transaction( fun from( map: Map, ) = Transaction( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - status = map["status"] as String, - operations = (map["operations"] as Number).toLong(), - expiresAt = map["expiresAt"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + status = (if (map.containsKey("status")) map["status"] else "pending") as String, + operations = ((if (map.containsKey("operations")) map["operations"] else 0) as Number).toLong(), + expiresAt = (if (map.containsKey("expiresAt")) map["expiresAt"] else "") as String, ) } } diff --git a/library/src/main/java/io/appwrite/models/TransactionList.kt b/library/src/main/java/io/appwrite/models/TransactionList.kt index c55a731..963f373 100644 --- a/library/src/main/java/io/appwrite/models/TransactionList.kt +++ b/library/src/main/java/io/appwrite/models/TransactionList.kt @@ -30,8 +30,8 @@ data class TransactionList( fun from( map: Map, ) = TransactionList( - total = (map["total"] as Number).toLong(), - transactions = (map["transactions"] as List>).map { Transaction.from(map = it) }, + total = ((if (map.containsKey("total")) map["total"] else 0) as Number).toLong(), + transactions = ((if (map.containsKey("transactions")) map["transactions"] else listOf()) as List>).map { Transaction.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 0eea3b0..22b4aeb 100644 --- a/library/src/main/java/io/appwrite/models/User.kt +++ b/library/src/main/java/io/appwrite/models/User.kt @@ -264,31 +264,31 @@ data class User( map: Map, nestedType: Class ) = User( - id = map["\$id"] as String, - createdAt = map["\$createdAt"] as String, - updatedAt = map["\$updatedAt"] as String, - name = map["name"] as String, + id = (if (map.containsKey("\$id")) map["\$id"] else "") as String, + createdAt = (if (map.containsKey("\$createdAt")) map["\$createdAt"] else "") as String, + updatedAt = (if (map.containsKey("\$updatedAt")) map["\$updatedAt"] else "") as String, + name = (if (map.containsKey("name")) map["name"] else "") as String, password = map["password"] as? String, hash = map["hash"] as? String, hashOptions = map["hashOptions"] as? Map, - 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, + registration = (if (map.containsKey("registration")) map["registration"] else "") as String, + status = (if (map.containsKey("status")) map["status"] else true) as Boolean, + labels = (if (map.containsKey("labels")) map["labels"] else listOf()) as List, + passwordUpdate = (if (map.containsKey("passwordUpdate")) map["passwordUpdate"] else "") as String, + email = (if (map.containsKey("email")) map["email"] else "") as String, + phone = (if (map.containsKey("phone")) map["phone"] else "") as String, + emailVerification = (if (map.containsKey("emailVerification")) map["emailVerification"] else false) 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, + phoneVerification = (if (map.containsKey("phoneVerification")) map["phoneVerification"] else false) as Boolean, + mfa = (if (map.containsKey("mfa")) map["mfa"] else false) 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, + targets = ((if (map.containsKey("targets")) map["targets"] else listOf()) as List>).map { Target.from(map = it) }, + accessedAt = (if (map.containsKey("accessedAt")) map["accessedAt"] else "") as String, impersonator = map["impersonator"] as? Boolean, impersonatorUserId = map["impersonatorUserId"] as? String, ) diff --git a/library/src/main/java/io/appwrite/services/Account.kt b/library/src/main/java/io/appwrite/services/Account.kt index 8df3822..e6201c4 100644 --- a/library/src/main/java/io/appwrite/services/Account.kt +++ b/library/src/main/java/io/appwrite/services/Account.kt @@ -1200,6 +1200,219 @@ class Account(client: Client) : Service(client) { nestedType = classOf(), ) + /** + * Get the list of verified passkeys registered by the currently logged in user. + * + * @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 and order on the following attributes: $id, $createdAt, $updatedAt, name, accessedAt + * @param total When set to false, the total count returned will be 0 and will not be calculated. + * @return [io.appwrite.models.PasskeyList] + */ + @JvmOverloads + suspend fun listPasskeys( + queries: List? = null, + total: Boolean? = null, + ): io.appwrite.models.PasskeyList { + val apiPath = "/account/passkeys" + 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.PasskeyList = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.PasskeyList.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.PasskeyList::class.java, + converter, + ) + } + + /** + * Start registering a passkey for the currently logged in user. The session must have signed in or completed an MFA challenge within the last 10 minutes. Pass the returned `publicKey` options to `navigator.credentials.create()`, then complete the registration with [Update passkey verification](/docs/references/cloud/client-web/account#updatePasskeyVerification). The passkey stays pending until verified, and the challenge expires after 5 minutes. + * + * @param passkeyId Passkey 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 Passkey name, shown when listing passkeys. Max length: 128 chars. + * @return [io.appwrite.models.PasskeyChallenge] + */ + @JvmOverloads + suspend fun createPasskey( + passkeyId: String? = null, + name: String? = null, + ): io.appwrite.models.PasskeyChallenge { + val apiPath = "/account/passkeys" + val apiParams = mutableMapOf( + "passkeyId" to passkeyId, + "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.PasskeyChallenge = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.PasskeyChallenge.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.PasskeyChallenge::class.java, + converter, + ) + } + + /** + * Get a verified passkey of the currently logged in user by its unique ID. + * + * @param passkeyId Passkey ID. + * @return [io.appwrite.models.Passkey] + */ + suspend fun getPasskey( + passkeyId: String, + ): io.appwrite.models.Passkey { + if (passkeyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"passkeyId\"") + } + val apiPath = ("/account/passkeys/{passkeyId}" + .replace("{passkeyId}", passkeyId) + ) + val apiParams = mutableMapOf() + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "accept" to "application/json", + ) + val converter: (Any) -> io.appwrite.models.Passkey = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Passkey.from(map = it as Map) + } + return client.call( + "GET", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Passkey::class.java, + converter, + ) + } + + /** + * Rename a passkey of the currently logged in user. + * + * @param passkeyId Passkey ID. + * @param name Passkey name. Max length: 128 chars. + * @return [io.appwrite.models.Passkey] + */ + suspend fun updatePasskey( + passkeyId: String, + name: String, + ): io.appwrite.models.Passkey { + if (passkeyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"passkeyId\"") + } + val apiPath = ("/account/passkeys/{passkeyId}" + .replace("{passkeyId}", passkeyId) + ) + val apiParams = mutableMapOf( + "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.Passkey = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Passkey.from(map = it as Map) + } + return client.call( + "PATCH", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Passkey::class.java, + converter, + ) + } + + /** + * Delete a passkey from the currently logged in user. The passkey can no longer be used to sign in, although it may remain stored on the user's device. The session must have signed in or completed an MFA challenge within the last 10 minutes. + * + * @param passkeyId Passkey ID. + * @return [Any] + */ + suspend fun deletePasskey( + passkeyId: String, + ): Any { + if (passkeyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"passkeyId\"") + } + val apiPath = ("/account/passkeys/{passkeyId}" + .replace("{passkeyId}", passkeyId) + ) + 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, + ) + } + + /** + * Complete a passkey registration started with [Create passkey](/docs/references/cloud/client-web/account#createPasskey). Pass the JSON form of the credential returned by `navigator.credentials.create()`, for example `credential.toJSON()`. Each registration can be verified once; a failed attempt requires starting it again. + * + * @param passkeyId Passkey ID. + * @param credential Registration credential returned by the authenticator, in the JSON form produced by `PublicKeyCredential.toJSON()`. + * @return [io.appwrite.models.Passkey] + */ + suspend fun updatePasskeyVerification( + passkeyId: String, + credential: Map, + ): io.appwrite.models.Passkey { + if (passkeyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"passkeyId\"") + } + val apiPath = ("/account/passkeys/{passkeyId}/verification" + .replace("{passkeyId}", passkeyId) + ) + val apiParams = mutableMapOf( + "credential" to credential, + ) + 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.Passkey = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.Passkey.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.Passkey::class.java, + converter, + ) + } + /** * Update currently logged in user password. For validation, user is required to pass in the new password, and the old password. For users created with OAuth, Team Invites and Magic URL, oldPassword is optional. * @@ -1784,6 +1997,7 @@ class Account(client: Client) : Service(client) { * @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. + * @param state An opaque value your app generates and keeps, for example in a cookie. It is returned unchanged as the `state` query parameter on the success and failure URLs, so your app can check that the sign-in it receives is one it started. Printable ASCII only (RFC 6749 Appendix A.5). Max length: 256 chars. */ @JvmOverloads suspend fun createOAuth2Session( @@ -1792,6 +2006,7 @@ class Account(client: Client) : Service(client) { success: String? = null, failure: String? = null, scopes: List? = null, + state: String? = null, ) { val apiPath = ("/account/sessions/oauth2/{provider}" .replace("{provider}", provider.value) @@ -1800,6 +2015,7 @@ class Account(client: Client) : Service(client) { "success" to success, "failure" to failure, "scopes" to scopes, + "state" to state, "project" to client.config["project"], ) val apiQuery = mutableListOf() @@ -1810,11 +2026,11 @@ class Account(client: Client) : Service(client) { } is List<*> -> { (it.value as List<*>).forEach { v -> - apiQuery.add("${it.key}[]=${v.toString()}") + apiQuery.add("${Uri.encode(it.key)}[]=${Uri.encode(v.toString())}") } } else -> { - apiQuery.add("${it.key}=${it.value.toString()}") + apiQuery.add("${Uri.encode(it.key)}=${Uri.encode(it.value.toString())}") } } } @@ -2268,6 +2484,7 @@ class Account(client: Client) : Service(client) { * @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. + * @param state An opaque value your app generates and keeps, for example in a cookie. It is returned unchanged as the `state` query parameter on the success and failure URLs, so your app can check that the sign-in it receives is one it started. Printable ASCII only (RFC 6749 Appendix A.5). Max length: 256 chars. */ @JvmOverloads suspend fun createOAuth2Token( @@ -2276,6 +2493,7 @@ class Account(client: Client) : Service(client) { success: String? = null, failure: String? = null, scopes: List? = null, + state: String? = null, ) { val apiPath = ("/account/tokens/oauth2/{provider}" .replace("{provider}", provider.value) @@ -2284,6 +2502,7 @@ class Account(client: Client) : Service(client) { "success" to success, "failure" to failure, "scopes" to scopes, + "state" to state, "project" to client.config["project"], ) val apiQuery = mutableListOf() @@ -2294,11 +2513,11 @@ class Account(client: Client) : Service(client) { } is List<*> -> { (it.value as List<*>).forEach { v -> - apiQuery.add("${it.key}[]=${v.toString()}") + apiQuery.add("${Uri.encode(it.key)}[]=${Uri.encode(v.toString())}") } } else -> { - apiQuery.add("${it.key}=${it.value.toString()}") + apiQuery.add("${Uri.encode(it.key)}=${Uri.encode(it.value.toString())}") } } } @@ -2332,6 +2551,68 @@ class Account(client: Client) : Service(client) { } } + /** + * Start signing in with a passkey. No email or user ID is needed: pass the returned `publicKey` options to `navigator.credentials.get()` and the user picks one of their passkeys. Then call [Update passkey token](/docs/references/cloud/client-web/account#updatePasskeyToken) with the credential to receive a token, and exchange it with [Create session](/docs/references/cloud/client-web/account#createSession). The challenge expires after 5 minutes. + * + * @return [io.appwrite.models.PasskeyChallenge] + */ + suspend fun createPasskeyToken(): io.appwrite.models.PasskeyChallenge { + val apiPath = "/account/tokens/passkey" + 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.PasskeyChallenge = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.PasskeyChallenge.from(map = it as Map) + } + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.PasskeyChallenge::class.java, + converter, + ) + } + + /** + * Complete a passkey sign-in started with [Create passkey token](/docs/references/cloud/client-web/account#createPasskeyToken). Pass the challenge ID and the JSON form of the credential returned by `navigator.credentials.get()`, for example `credential.toJSON()`. The returned token includes its secret and expires after 1 minute: exchange it for a session with [Create session](/docs/references/cloud/client-web/account#createSession). Passkeys require user verification, so the session also satisfies MFA. Each challenge can only be used once. + * + * @param challengeId Challenge ID returned by createPasskeyToken. + * @param credential Authentication credential returned by the authenticator, in the JSON form produced by `PublicKeyCredential.toJSON()`. + * @return [io.appwrite.models.TokenSecret] + */ + suspend fun updatePasskeyToken( + challengeId: String, + credential: Map, + ): io.appwrite.models.TokenSecret { + val apiPath = "/account/tokens/passkey" + val apiParams = mutableMapOf( + "challengeId" to challengeId, + "credential" to credential, + ) + 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.TokenSecret = { + @Suppress("UNCHECKED_CAST") + io.appwrite.models.TokenSecret.from(map = it as Map) + } + return client.call( + "PUT", + apiPath, + apiHeaders, + apiParams, + responseType = io.appwrite.models.TokenSecret::class.java, + converter, + ) + } + /** * 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. * diff --git a/library/src/main/java/io/appwrite/services/Analytics.kt b/library/src/main/java/io/appwrite/services/Analytics.kt new file mode 100644 index 0000000..1949f76 --- /dev/null +++ b/library/src/main/java/io/appwrite/services/Analytics.kt @@ -0,0 +1,83 @@ +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 Analytics(client: Client) : Service(client) { + /** + * Send a tracking event from a browser, native app, or server-side SDK. + * + * @param propertyId Analytics property ID. + * @param name Event name. "pageview" is just a conventional event name; events are not modeled specially. + * @param url Full page URL or screen identifier. + * @param domain Hostname (e.g. example.com). + * @param referrer Referrer URL. + * @param screenWidth Viewport width in CSS pixels. + * @param sessionHash Optional session hash (provided by SDK). + * @param scrollDepth Scroll depth percentage 0-100. + * @param engagementTime Engagement time in seconds, 0-4294967295. + * @param props Custom string properties as a flat key=value list (max 32 entries, alternating key,value). + * @param userId Override user ID. Requires API-key auth with analytics.write scope. + * @param ip Override IP address. Requires API-key auth with analytics.write scope. + * @param userAgent Override user agent. Requires API-key auth with analytics.write scope. + * @return [Any] + */ + @JvmOverloads + suspend fun createEvent( + propertyId: String, + name: String, + url: String, + domain: String? = null, + referrer: String? = null, + screenWidth: Long? = null, + sessionHash: String? = null, + scrollDepth: Long? = null, + engagementTime: Long? = null, + props: List? = null, + userId: String? = null, + ip: String? = null, + userAgent: String? = null, + ): Any { + if (propertyId.isEmpty()) { + throw AppwriteException("Missing required parameter: \"propertyId\"") + } + val apiPath = ("/analytics/properties/{propertyId}/events" + .replace("{propertyId}", propertyId) + ) + val apiParams = mutableMapOf( + "name" to name, + "url" to url, + "domain" to domain, + "referrer" to referrer, + "screenWidth" to screenWidth, + "sessionHash" to sessionHash, + "scrollDepth" to scrollDepth, + "engagementTime" to engagementTime, + "props" to props, + "userId" to userId, + "ip" to ip, + "userAgent" to userAgent, + ) + val apiHeaders = mutableMapOf( + "X-Appwrite-Project" to client.config["project"].orEmpty(), + "content-type" to "application/json", + "accept" to "application/json", + ) + return client.call( + "POST", + apiPath, + apiHeaders, + apiParams, + responseType = Any::class.java, + ) + } +} diff --git a/library/src/main/java/io/appwrite/services/Oauth2.kt b/library/src/main/java/io/appwrite/services/Oauth2.kt index 030f4ce..f5cbc3c 100644 --- a/library/src/main/java/io/appwrite/services/Oauth2.kt +++ b/library/src/main/java/io/appwrite/services/Oauth2.kt @@ -85,7 +85,7 @@ class Oauth2(client: Client) : Service(client) { prompt: String? = null, maxAge: Long? = null, authorizationDetails: String? = null, - resource: String? = null, + resource: List? = null, audience: String? = null, requestUri: String? = null, ): io.appwrite.models.Oauth2Authorize { @@ -157,7 +157,7 @@ class Oauth2(client: Client) : Service(client) { prompt: String? = null, maxAge: Long? = null, authorizationDetails: String? = null, - resource: String? = null, + resource: List? = null, audience: String? = null, requestUri: String? = null, ): io.appwrite.models.Oauth2Authorize { @@ -213,7 +213,7 @@ class Oauth2(client: Client) : Service(client) { clientId: String? = null, scope: String? = null, authorizationDetails: String? = null, - resource: String? = null, + resource: List? = null, audience: String? = null, ): io.appwrite.models.Oauth2DeviceAuthorization { val apiPath = ("/oauth2/{project_id}/device_authorization" @@ -468,7 +468,7 @@ class Oauth2(client: Client) : Service(client) { prompt: String? = null, maxAge: Long? = null, authorizationDetails: String? = null, - resource: String? = null, + resource: List? = null, audience: String? = null, ): io.appwrite.models.Oauth2PAR { val apiPath = ("/oauth2/{project_id}/par" @@ -642,7 +642,7 @@ class Oauth2(client: Client) : Service(client) { clientSecret: String? = null, codeVerifier: String? = null, redirectUri: String? = null, - resource: String? = null, + resource: List? = null, audience: String? = null, ): io.appwrite.models.Oauth2Token { val apiPath = ("/oauth2/{project_id}/token" diff --git a/library/src/main/java/io/appwrite/services/Push.kt b/library/src/main/java/io/appwrite/services/Push.kt index 48488c0..21409d7 100644 --- a/library/src/main/java/io/appwrite/services/Push.kt +++ b/library/src/main/java/io/appwrite/services/Push.kt @@ -14,7 +14,6 @@ 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 @@ -338,7 +337,7 @@ class Push( /** * Subscribe to a single topic built with [io.appwrite.Topic], e.g. - * `push.subscribe(Topic.path(listOf("user", userId)).any()) { message -> ... }`. + * `push.subscribe(Topic.path(listOf("user", userId))) { message -> ... }`. * Returns a [PushSubscription] handle; see the `List` overload for the options. */ @JvmOverloads @@ -352,28 +351,12 @@ class Push( ): PushSubscription = subscribe(listOf(topic.toString()), background, title, retry, notifyInForeground, 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, - notifyInForeground: Boolean = false, - callback: (PushMessage) -> Unit, - ): PushSubscription = - subscribe(listOf(topic.toString()), background, title, retry, notifyInForeground, 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. + * `push.subscribe(listOf("news", Topic.path(listOf("chat", chatId)))) { message -> ... }`. + * Each element must be a `String` or `Topic` (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") @@ -386,8 +369,8 @@ class Push( 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}" + require(topic is String || topic is Topic) { + "topics[$index] must be a String or Topic, got ${topic::class.simpleName}" } topic.toString() } @@ -397,8 +380,8 @@ class Push( /** * 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()) { ... }`. + * Pass raw strings, or build them with [io.appwrite.Topic] + * through the `Topic` overloads, e.g. `push.subscribe(Topic.path(listOf("user", userId))) { ... }`. * * 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 @@ -828,10 +811,14 @@ class Push( const val EXTRA_PAYLOAD = PushBackground.EXTRA_PAYLOAD /** - * What background delivery can rely on: exact alarms, battery-optimisation exemption and - * foreground mode. When [PushBackgroundStatus.bestEffort] is true, wake-ups are inexact and - * messages can arrive late while the app is closed; explain why before asking with - * [requestExactAlarms] or [requestIgnoreBatteryOptimizations]. + * What background delivery can rely on: exact alarms, battery-optimisation exemption, + * foreground mode and restricted background usage. When [PushBackgroundStatus.bestEffort] + * is true, messages can arrive late while the app is closed; explain why before asking with + * [requestExactAlarms] or [requestIgnoreBatteryOptimizations]. When + * [PushBackgroundStatus.backgroundRestricted] is true, neither exact alarms nor foreground + * mode lift it: ask the user to choose Unrestricted in the app's battery settings + * (Settings > Apps > your app > Battery), or for the battery-optimisation exemption, which + * also lets the SDK's alarms and jobs run. */ @JvmStatic fun backgroundStatus(context: Context): PushBackgroundStatus = PushBackground.backgroundStatus(context) diff --git a/library/src/main/java/io/appwrite/services/PushBackground.kt b/library/src/main/java/io/appwrite/services/PushBackground.kt index e06dfab..73103d5 100644 --- a/library/src/main/java/io/appwrite/services/PushBackground.kt +++ b/library/src/main/java/io/appwrite/services/PushBackground.kt @@ -52,8 +52,15 @@ 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?) +/** + * What a message asks a background notification to show. A [raw] payload, one that is not a JSON + * object, is shown as the body under the subscription's title; an envelope is [visible] only with a + * title or body in its `notification`. + */ +internal data class PushNotificationContent(val raw: Boolean, val title: String?, val body: String?, val image: String?) { + val visible: Boolean + get() = raw || title != null || body != null +} /** A background subscription saved across restarts: its filter, QoS choice and notification title. */ internal data class PushEntry( @@ -887,7 +894,7 @@ internal object PushBackground { // 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) { + internal 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. @@ -952,16 +959,21 @@ internal object PushBackground { /** * 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. + * the server's title, body and image from [content], falling back to [title], and to the payload + * as the body when it is not a JSON object. An envelope with neither a title nor a body, such as + * a data-only push, posts nothing. */ fun notify(context: Context, message: PushMessage, title: String, content: PushNotificationContent = notificationContent(message)) { + if (!content.visible) { + return + } 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 body = content.body ?: message.data.takeIf { content.raw } val builder = NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle(content.title ?: title) .setSmallIcon(notificationIcon(context)) @@ -992,12 +1004,13 @@ internal object PushBackground { .also { ActivityManager.getMyMemoryState(it) } .importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND - /** The server's `notification` block in [message]: nulls when the payload has none or is not JSON. */ + /** The server's `notification` block in [message], [PushNotificationContent.raw] when the payload is not a JSON object. */ 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")) + val envelope = runCatching { JSONObject(message.data) }.getOrNull() + ?: return PushNotificationContent(true, null, null, null) + val notification = envelope.optJSONObject("notification") + fun field(name: String) = (notification?.opt(name) as? String)?.takeIf { it.isNotEmpty() } + return PushNotificationContent(false, field("title"), field("body"), field("image")) } // Downloads notification images, so a slow one is abandoned without holding up delivery. @@ -1201,6 +1214,8 @@ internal object PushBackground { app.getSystemService(AlarmManager::class.java)?.canScheduleExactAlarms() == true, ignoringBatteryOptimizations = app.getSystemService(PowerManager::class.java)?.isIgnoringBatteryOptimizations(app.packageName) == true, foregroundService = PushStore.foreground(app), + backgroundRestricted = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + app.getSystemService(ActivityManager::class.java)?.isBackgroundRestricted == true, ) } diff --git a/library/src/main/java/io/appwrite/services/PushCore.kt b/library/src/main/java/io/appwrite/services/PushCore.kt index 15f86b6..bc15129 100644 --- a/library/src/main/java/io/appwrite/services/PushCore.kt +++ b/library/src/main/java/io/appwrite/services/PushCore.kt @@ -54,19 +54,36 @@ data class PushMessage( /** * What background delivery can rely on. Without exact alarms, the scheduled wake-ups are inexact * and Doze can defer them, so delivery is [bestEffort] unless foreground mode keeps the - * connection open. + * connection open. When the user restricted background usage, delivery is [bestEffort] unless the + * app is also exempt from battery optimisation, and foreground mode no longer counts: Android stops + * a restricted app's foreground service once it leaves the screen, exemption or not. */ -data class PushBackgroundStatus( +data class PushBackgroundStatus @JvmOverloads constructor( /** The app may schedule exact alarms (`SCHEDULE_EXACT_ALARM`, granted). */ val exactAlarms: Boolean, /** The app is exempt from battery optimisation. */ val ignoringBatteryOptimizations: Boolean, /** Foreground mode (`setForeground(true)`) keeps the connection open in a service. */ val foregroundService: Boolean, + /** + * The user restricted the app's background usage in its battery settings (Android 9+). Android + * may then hold its alarms, jobs and foreground service while it is closed, so messages can + * wait until the app is opened. The battery-optimisation exemption lets the alarms and jobs + * run again, but foreground mode still stops once the app leaves the screen. + */ + val backgroundRestricted: Boolean = false, ) { - /** Wake-ups may be deferred by Doze, so messages can arrive late while the app is closed. */ + /** + * Messages can arrive late, or not until the app is opened, while the app is closed: background + * usage is restricted without the battery-optimisation exemption, or wake-ups may be deferred + * by Doze. + */ val bestEffort: Boolean - get() = !exactAlarms && !foregroundService + get() { + val held = backgroundRestricted && !ignoringBatteryOptimizations + val foreground = foregroundService && !backgroundRestricted + return held || (!exactAlarms && !foreground) + } } /** Connection parameters shared between the in-process client and the foreground service. */ diff --git a/library/src/main/java/io/appwrite/services/Teams.kt b/library/src/main/java/io/appwrite/services/Teams.kt index 7227eba..8a4bb87 100644 --- a/library/src/main/java/io/appwrite/services/Teams.kt +++ b/library/src/main/java/io/appwrite/services/Teams.kt @@ -539,6 +539,7 @@ class Teams(client: Client) : Service(client) { * @param phone Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212. * @param url URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. 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. * @param name Name of the new team member. Max length: 128 chars. + * @param membershipId Membership 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. * @return [io.appwrite.models.Membership] */ @JvmOverloads @@ -550,6 +551,7 @@ class Teams(client: Client) : Service(client) { phone: String? = null, url: String? = null, name: String? = null, + membershipId: String? = null, ): io.appwrite.models.Membership { if (teamId.isEmpty()) { throw AppwriteException("Missing required parameter: \"teamId\"") @@ -564,6 +566,7 @@ class Teams(client: Client) : Service(client) { "roles" to roles, "url" to url, "name" to name, + "membershipId" to membershipId, ) val apiHeaders = mutableMapOf( "X-Appwrite-Project" to client.config["project"].orEmpty(), diff --git a/library/src/test/java/io/appwrite/QueryTest.kt b/library/src/test/java/io/appwrite/QueryTest.kt new file mode 100644 index 0000000..a3662b7 --- /dev/null +++ b/library/src/test/java/io/appwrite/QueryTest.kt @@ -0,0 +1,171 @@ +package io.appwrite + +import com.google.gson.JsonParser +import org.junit.Assert.assertEquals +import org.junit.Test + +class QueryTest { + private fun method(json: String): String = + JsonParser.parseString(json).asJsonObject.get("method").asString + + @Test + fun countDistinct() { + assertEquals("countDistinct", method(Query.countDistinct("year", "uniqueYears"))) + } + + @Test + fun sum() { + assertEquals("sum", method(Query.sum("price", "total"))) + } + + @Test + fun avg() { + assertEquals("avg", method(Query.avg("price", "avgPrice"))) + } + + @Test + fun min() { + assertEquals("min", method(Query.min("price", "lowest"))) + } + + @Test + fun max() { + assertEquals("max", method(Query.max("price", "highest"))) + } + + @Test + fun stddev() { + assertEquals("stddev", method(Query.stddev("price", "sd"))) + } + + @Test + fun stddevPop() { + assertEquals("stddevPop", method(Query.stddevPop("price", "sdp"))) + } + + @Test + fun stddevSamp() { + assertEquals("stddevSamp", method(Query.stddevSamp("price", "sds"))) + } + + @Test + fun variance() { + assertEquals("variance", method(Query.variance("price", "var"))) + } + + @Test + fun varPop() { + assertEquals("varPop", method(Query.varPop("price", "vp"))) + } + + @Test + fun varSamp() { + assertEquals("varSamp", method(Query.varSamp("price", "vs"))) + } + + @Test + fun bitAnd() { + assertEquals("bitAnd", method(Query.bitAnd("flags", "band"))) + } + + @Test + fun bitOr() { + assertEquals("bitOr", method(Query.bitOr("flags", "bor"))) + } + + @Test + fun bitXor() { + assertEquals("bitXor", method(Query.bitXor("flags", "bxor"))) + } + + @Test + fun having() { + val json = JsonParser.parseString(Query.having(listOf(Query.greaterThan("total", 1)))).asJsonObject + assertEquals("having", json.get("method").asString) + assertEquals("greaterThan", json.get("values").asJsonArray[0].asJsonObject.get("method").asString) + } + + @Test + fun leftJoin() { + assertEquals("leftJoin", method(Query.leftJoin("orders", "\$id", "customerId", "=", "ord"))) + } + + @Test + fun rightJoin() { + assertEquals("rightJoin", method(Query.rightJoin("orders", "\$id", "customerId"))) + } + + @Test + fun fullOuterJoin() { + assertEquals("fullOuterJoin", method(Query.fullOuterJoin("orders", "\$id", "customerId"))) + } + + @Test + fun crossJoin() { + assertEquals("crossJoin", method(Query.crossJoin("orders", "ord"))) + } + + @Test + fun on() { + assertEquals("on", method(Query.on("\$id", "customerId"))) + } + + @Test + fun relationship() { + val query = Query.relationship("comments", listOf(Query.equal("approved", true), Query.limit(2))) + val json = JsonParser.parseString(query).asJsonObject + assertEquals("relationship", json.get("method").asString) + assertEquals("comments", json.get("attribute").asString) + val values = json.get("values").asJsonArray + assertEquals("equal", values[0].asJsonObject.get("method").asString) + assertEquals("limit", values[1].asJsonObject.get("method").asString) + assertEquals(listOf(query), Query.builder().relationship("comments", listOf(Query.equal("approved", true), Query.limit(2))).build()) + } + + @Test + fun nestedLeftJoin() { + val json = JsonParser.parseString( + Query.leftJoin( + "orders", + "ord", + listOf( + Query.on("\$id", "customerId"), + Query.equal("ord.status", "paid"), + ) + ) + ).asJsonObject + assertEquals("leftJoin", json.get("method").asString) + val values = json.get("values").asJsonArray + assertEquals("ord", values[0].asString) + assertEquals("on", values[1].asJsonObject.get("method").asString) + assertEquals("equal", values[2].asJsonObject.get("method").asString) + } + + @Test + fun notCovers() { + assertEquals("notCovers", method(Query.notCovers("location", listOf(1, 2)))) + } + + @Test + fun spatialEquals() { + assertEquals("spatialEquals", method(Query.spatialEquals("location", listOf(1, 2)))) + } + + @Test + fun notSpatialEquals() { + assertEquals("notSpatialEquals", method(Query.notSpatialEquals("location", listOf(1, 2)))) + } + + @Test + fun page() { + val queries = Query.page(2, 10) + assertEquals(2, queries.size) + assertEquals("limit", method(queries[0])) + assertEquals("offset", method(queries[1])) + } + + @Test(expected = ArithmeticException::class) + fun pageRejectsAnOffsetPastIntMax() { + Query.page(50_000_000, 50) + } +} diff --git a/library/src/test/java/io/appwrite/TopicTest.kt b/library/src/test/java/io/appwrite/TopicTest.kt index 0dd65bf..3c2a51c 100644 --- a/library/src/test/java/io/appwrite/TopicTest.kt +++ b/library/src/test/java/io/appwrite/TopicTest.kt @@ -7,7 +7,7 @@ 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) } + assertThrows(IllegalArgumentException::class.java) { Topic.path(listOf("user")).path(levels) } } @Test @@ -23,23 +23,6 @@ class TopicTest { ) } - @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()) @@ -49,5 +32,6 @@ class TopicTest { assertPathFails(listOf("user", "a+b")) assertPathFails(listOf("user", "#")) assertPathFails(listOf("a#b")) + assertPathFails(listOf("user", "a\u0000b")) } }