Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change Log

## 28.2.0

* Updated: requests send `X-Appwrite-Response-Format: 2.6.2`; the SDK targets Appwrite 2.6.x
* Fixed: path parameters are percent-encoded as one segment; `.` and `..` throw `AppwriteException`
* Fixed: chunked upload resume lookups percent-encode the file ID
* Fixed: examples pass `null` for unset optional params instead of empty strings

## 28.1.0

* Added: `createQuery` on `databases`, `documentsDB` and `tablesDB` to send list queries in the request body
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[![pub package](https://img.shields.io/pub/v/appwrite?style=flat-square)](https://pub.dartlang.org/packages/appwrite)
![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-2.5.x-blue.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-2.6.x-blue.svg?style=flat-square)
[![X Account](https://img.shields.io/badge/follow-%40appwrite-000000?logo=x&style=flat-square)](https://x.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.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-flutter/releases) when APIs differ.
**This SDK targets Appwrite server version 2.6.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-flutter/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 Flutter 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)

Expand All @@ -24,7 +24,7 @@ Or add it to your app's `pubspec.yaml` file and run `flutter pub get`:

```yaml
dependencies:
appwrite: ^28.1.0
appwrite: ^28.2.0
```

### Android requirements
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// The native half of Push on Android: background delivery that survives the process being
// killed, a reboot and an app update, shared with the Appwrite Android SDK.
group = "io.appwrite.flutter"
version = "28.1.0"
version = "28.2.0"

buildscript {
ext.kotlin_version = "2.1.0"
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/oauth2/authorize-post.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Oauth2 oauth2 = Oauth2(client);
models.Oauth2Authorize result = await oauth2.authorizePost(
clientId: '<CLIENT_ID>', // optional
redirectUri: 'https://example.com', // optional
responseType: '', // optional
responseType: null, // optional
scope: '<SCOPE>', // optional
state: '<STATE>', // optional
nonce: '<NONCE>', // optional
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/oauth2/authorize.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Oauth2 oauth2 = Oauth2(client);
models.Oauth2Authorize result = await oauth2.authorize(
clientId: '<CLIENT_ID>', // optional
redirectUri: 'https://example.com', // optional
responseType: '', // optional
responseType: null, // optional
scope: '<SCOPE>', // optional
state: '<STATE>', // optional
nonce: '<NONCE>', // optional
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/storage/update-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Storage storage = Storage(client);
models.File result = await storage.updateFile(
bucketId: '<BUCKET_ID>',
fileId: '<FILE_ID>',
name: '<NAME>', // optional
name: null, // optional
permissions: [Permission.read(Role.any())], // optional
);
```
9 changes: 8 additions & 1 deletion lib/appwrite.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// Appwrite Flutter SDK
///
/// This SDK targets Appwrite server version 2.5.x as shipped on Appwrite Cloud.
/// This SDK targets Appwrite server version 2.6.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-flutter/releases)
Expand Down Expand Up @@ -58,3 +58,10 @@ part 'services/storage.dart';
part 'services/tables_db.dart';
part 'services/teams.dart';
part 'services/vectors_db.dart';

String _encodePath(String value) {
if (value == '.' || value == '..') {
throw AppwriteException('Path parameter cannot be "." or ".."');
}
return Uri.encodeComponent(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add coverage for slash and dot-segment IDs

The service path tests use angle-bracket IDs, but none exercise an ID containing / or the exact . and .. cases. Those are the key cases for verifying that IDs remain one path segment and that dot-segment IDs are rejected, so a regression in this new helper could pass the current tests.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/appwrite.dart
Line: 66

Comment:
**Add coverage for slash and dot-segment IDs**

The service path tests use angle-bracket IDs, but none exercise an ID containing `/` or the exact `.` and `..` cases. Those are the key cases for verifying that IDs remain one path segment and that dot-segment IDs are rejected, so a regression in this new helper could pass the current tests.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

🟡 Minor · testing · Reply if this doesn't apply.

}
50 changes: 25 additions & 25 deletions lib/services/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class Account extends Service {

final String apiPath = '/account/consents/{consentId}'.replaceAll(
'{consentId}',
consentId,
_encodePath(consentId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -139,7 +139,7 @@ class Account extends Service {

final String apiPath = '/account/consents/{consentId}'.replaceAll(
'{consentId}',
consentId,
_encodePath(consentId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -176,7 +176,7 @@ class Account extends Service {

final String apiPath = '/account/consents/{consentId}/tokens'.replaceAll(
'{consentId}',
consentId,
_encodePath(consentId),
);

final Map<String, dynamic> apiParams = {
Expand Down Expand Up @@ -220,11 +220,11 @@ class Account extends Service {
final String apiPath = '/account/consents/{consentId}/tokens/{tokenId}'
.replaceAll(
'{consentId}',
consentId,
_encodePath(consentId),
)
.replaceAll(
'{tokenId}',
tokenId,
_encodePath(tokenId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -266,11 +266,11 @@ class Account extends Service {
final String apiPath = '/account/consents/{consentId}/tokens/{tokenId}'
.replaceAll(
'{consentId}',
consentId,
_encodePath(consentId),
)
.replaceAll(
'{tokenId}',
tokenId,
_encodePath(tokenId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -364,7 +364,7 @@ class Account extends Service {

final String apiPath = '/account/identities/{identityId}'.replaceAll(
'{identityId}',
identityId,
_encodePath(identityId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -480,7 +480,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll(
'{type}',
type.value,
_encodePath(type.value),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -510,7 +510,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll(
'{type}',
type.value,
_encodePath(type.value),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -543,7 +543,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll(
'{type}',
type.value,
_encodePath(type.value),
);

final Map<String, dynamic> apiParams = {
Expand Down Expand Up @@ -575,7 +575,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll(
'{type}',
type.value,
_encodePath(type.value),
);

final Map<String, dynamic> apiParams = {
Expand Down Expand Up @@ -607,7 +607,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll(
'{type}',
type.value,
_encodePath(type.value),
);

final Map<String, dynamic> apiParams = {};
Expand All @@ -634,7 +634,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll(
'{type}',
type.value,
_encodePath(type.value),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -1086,7 +1086,7 @@ class Account extends Service {

final String apiPath = '/account/passkeys/{passkeyId}'.replaceAll(
'{passkeyId}',
passkeyId,
_encodePath(passkeyId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -1119,7 +1119,7 @@ class Account extends Service {

final String apiPath = '/account/passkeys/{passkeyId}'.replaceAll(
'{passkeyId}',
passkeyId,
_encodePath(passkeyId),
);

final Map<String, dynamic> apiParams = {
Expand Down Expand Up @@ -1157,7 +1157,7 @@ class Account extends Service {

final String apiPath = '/account/passkeys/{passkeyId}'.replaceAll(
'{passkeyId}',
passkeyId,
_encodePath(passkeyId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -1196,7 +1196,7 @@ class Account extends Service {
final String apiPath = '/account/passkeys/{passkeyId}/verification'
.replaceAll(
'{passkeyId}',
passkeyId,
_encodePath(passkeyId),
);

final Map<String, dynamic> apiParams = {
Expand Down Expand Up @@ -1748,7 +1748,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/sessions/oauth2/{provider}'.replaceAll(
'{provider}',
provider.value,
_encodePath(provider.value),
);

final Map<String, dynamic> params = {
Expand Down Expand Up @@ -1861,7 +1861,7 @@ class Account extends Service {

final String apiPath = '/account/sessions/{sessionId}'.replaceAll(
'{sessionId}',
sessionId,
_encodePath(sessionId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -1895,7 +1895,7 @@ class Account extends Service {

final String apiPath = '/account/sessions/{sessionId}'.replaceAll(
'{sessionId}',
sessionId,
_encodePath(sessionId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -1932,7 +1932,7 @@ class Account extends Service {

final String apiPath = '/account/sessions/{sessionId}'.replaceAll(
'{sessionId}',
sessionId,
_encodePath(sessionId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -2037,7 +2037,7 @@ class Account extends Service {

final String apiPath = '/account/targets/{targetId}/push'.replaceAll(
'{targetId}',
targetId,
_encodePath(targetId),
);

final Map<String, dynamic> apiParams = {
Expand Down Expand Up @@ -2076,7 +2076,7 @@ class Account extends Service {

final String apiPath = '/account/targets/{targetId}/push'.replaceAll(
'{targetId}',
targetId,
_encodePath(targetId),
);

final Map<String, dynamic> apiParams = {};
Expand Down Expand Up @@ -2210,7 +2210,7 @@ class Account extends Service {
}) async {
final String apiPath = '/account/tokens/oauth2/{provider}'.replaceAll(
'{provider}',
provider.value,
_encodePath(provider.value),
);

final Map<String, dynamic> params = {
Expand Down
2 changes: 1 addition & 1 deletion lib/services/analytics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Analytics extends Service {
final String apiPath = '/analytics/properties/{propertyId}/events'
.replaceAll(
'{propertyId}',
propertyId,
_encodePath(propertyId),
);

final Map<String, dynamic> apiParams = {
Expand Down
Loading
Loading