Compare commits

...
Author SHA1 Message Date
Chirag Aggarwal 3bbd87ecb2 fix: openapi too 2025-11-13 16:05:27 +05:30
Chirag Aggarwal fe8c4cb479 fix: generation of all response models in case of multi method endpoints 2025-11-13 15:59:12 +05:30
Damodar LohaniandGitHub b07674ccd3 Merge pull request #10812 from appwrite/fix-upgrade-utopia-database
Upgrade utopia-php database
2025-11-13 13:31:29 +05:45
Damodar Lohani aa5e3bf9a9 Merge remote-tracking branch 'origin/1.8.x' into fix-upgrade-utopia-database 2025-11-13 07:00:45 +00:00
Damodar Lohani c1b50e9941 Upgrade utopia-php database 2025-11-13 06:59:03 +00:00
Jake BarnbyandGitHub 3a76249d80 Merge pull request #10811 from appwrite/release-flutter-dart-add-screenshot-examples
release flutter + dart and add screenshot api docs
2025-11-13 06:58:32 +00:00
Chirag Aggarwal b2991709dc update version 2025-11-13 11:38:50 +05:30
Chirag Aggarwal fcc07533e1 update changelog 2025-11-13 11:34:35 +05:30
Chirag Aggarwal 3a6d907b42 release flutter + dart and add screenshot api docs 2025-11-13 11:29:38 +05:30
Damodar LohaniandGitHub a9c9f602ec Merge pull request #10807 from appwrite/feat-update-flutter-default-build-runtime
Update Flutter framework default build runtime version to 3.35
2025-11-12 13:16:20 +05:45
Damodar LohaniandGitHub d90b4c2f19 update framework config 2025-11-12 12:43:34 +05:45
Damodar LohaniandGitHub 79d733486c Update Flutter framework default build runtime version to 3.35 2025-11-12 12:35:31 +05:45
Damodar LohaniandGitHub d862245090 Merge pull request #10806 from appwrite/lohanidamodar-patch-2
Fix: add default fallback to Flutter framework
2025-11-12 12:30:11 +05:45
Damodar LohaniandGitHub aacaadcce0 Fix: add default fallback to Flutter framework 2025-11-12 12:17:15 +05:45
Damodar LohaniandGitHub 69ee6e0659 Merge pull request #10805 from appwrite/lohanidamodar-patch-2
Feat: enable Flutter 3.35 and Dart 3.9 for sites
2025-11-12 10:51:00 +05:45
Damodar LohaniandGitHub cfd62cf0a1 Update template-runtimes.php 2025-11-12 09:32:12 +05:45
Damodar LohaniandGitHub 279682941a enable dart 2025-11-12 09:31:04 +05:45
Damodar LohaniandGitHub eae26d90af Feat: enable Flutter 3.35 for sites 2025-11-12 09:25:35 +05:45
Jake BarnbyandGitHub b46ebb95a3 Merge pull request #10688 from appwrite/users-add-attributes
Users add new email attributes
2025-11-12 03:19:24 +00:00
Damodar LohaniandGitHub ffcb6a4677 Merge pull request #10804 from appwrite/feat-upgrade-runtimes
Feat: upgrade runtime
2025-11-12 07:26:17 +05:45
Damodar LohaniandGitHub 142e359dba Merge branch '1.8.x' into feat-upgrade-runtimes 2025-11-12 07:16:09 +05:45
Jake BarnbyandGitHub 4130983210 Merge pull request #10407 from appwrite/dat-647
updated migration error attribute size
2025-11-12 01:05:13 +00:00
Damodar Lohani e01ac65ce8 Feat: upgrade runtime 2025-11-12 00:26:27 +00:00
fogelito 97454c0768 EmailValidator chnage to email 2025-11-11 15:33:16 +02:00
fogelito 8b4a56cd9b $emailCanonical 2025-11-11 15:25:10 +02:00
fogelito da0a6b167f EmailValidator 2025-11-11 15:00:09 +02:00
fogelito 27d0c511ac patch users email 2025-11-11 12:09:40 +02:00
fogelito 8a58f7b6d7 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into users-add-attributes 2025-11-11 11:36:01 +02:00
fogelito cc97d78b5a Add try catch 2025-11-11 11:35:18 +02:00
Luke B. SilverandGitHub 91c24b671b Merge pull request #10791 from appwrite/release-cli-php
chore: release cli and php
2025-11-11 09:22:57 +00:00
fogelito b28e536967 Add try catch 2025-11-11 10:35:39 +02:00
fogelito ce7f40dd34 Revert response model 2025-11-11 08:16:09 +02:00
fogelito 49318b5dac Merge branch '1.8.x' of https://github.com/appwrite/appwrite into users-add-attributes 2025-11-11 08:09:11 +02:00
fogelito 6096c42ff9 More updates 2025-11-10 17:30:40 +02:00
fogelito 5afae248ae Test Update 2025-11-10 15:00:18 +02:00
fogelito 98255fe628 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into users-add-attributes
# Conflicts:
#	composer.lock
2025-11-10 14:52:25 +02:00
ArnabChatterjee20k 7c408d675e pr followups 2025-11-07 14:30:22 +05:30
ArnabChatterjee20k 4d47bc2ebf Merge remote-tracking branch 'upstream/1.8.x' into dat-647 2025-11-07 13:48:59 +05:30
ArnabChatterjee20k 119d6c1789 Merge remote-tracking branch 'upstream/1.8.x' into dat-647 2025-11-07 13:19:28 +05:30
fogelito 1f0e3e0f51 lock file 2025-11-05 13:21:52 +02:00
fogelito fe9d49c346 Response Model 2025-11-04 17:13:53 +02:00
fogelito 6a41f8b2b9 Remove todos 2025-11-02 11:16:12 +02:00
fogelito 503145734b Move user Document out of try block 2025-11-02 11:06:56 +02:00
fogelito 371cab0658 try catch EmailCanonical 2025-11-02 10:59:28 +02:00
fogelito ca8cb6f260 Add utopia-php/emails 2025-11-02 09:13:55 +02:00
fogelito fea1630909 Remove redundant emailVerification 2025-10-30 18:18:09 +02:00
fogelito e40e88c322 Update emails attributes 2025-10-30 18:09:36 +02:00
fogelito 037d60633b Add emailCanonical 2025-10-27 15:59:20 +02:00
fogelito 218bb4c537 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into users-add-attributes 2025-10-27 13:37:33 +02:00
fogelito c13a70e226 Add users attributes 2025-10-23 12:38:18 +03:00
ArnabChatterjee20k ac604b11e3 added migration script 2025-09-01 13:13:52 +05:30
ArnabChatterjee20k f68d49c4d6 updated the migration error size attribute 2025-08-29 18:19:29 +05:30
44 changed files with 1041 additions and 123 deletions
+55
View File
@@ -364,6 +364,61 @@ return [
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('emailCanonical'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 320,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('emailIsFree'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('emailIsDisposable'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('emailIsCorporate'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('emailIsCanonical'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
+1 -1
View File
@@ -2345,7 +2345,7 @@ return [
'$id' => ID::custom('errors'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'size' => 1_000_000,
'signed' => true,
'required' => true,
'default' => null,
+2 -1
View File
@@ -273,7 +273,7 @@ return [
'key' => 'flutter',
'name' => 'Flutter',
'screenshotSleep' => 5000,
'buildRuntime' => 'flutter-3.29',
'buildRuntime' => 'flutter-3.35',
'runtimes' => getVersions($templateRuntimes['FLUTTER']['versions'], 'flutter'),
'adapters' => [
'static' => [
@@ -282,6 +282,7 @@ return [
'installCommand' => 'flutter pub get',
'outputDirectory' => './build/web',
'startCommand' => 'bash helpers/server.sh',
'fallbackFile' => 'index.html'
],
],
],
+2 -2
View File
@@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '20.3.0',
'version' => '20.3.1',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -376,7 +376,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '19.3.0',
'version' => '19.4.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
+12 -4
View File
@@ -13105,6 +13105,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13131,7 +13132,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -13746,6 +13748,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13772,7 +13775,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -30954,6 +30958,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -30980,7 +30985,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -31601,6 +31607,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -31627,7 +31634,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+12 -4
View File
@@ -12127,6 +12127,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12153,7 +12154,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -12529,6 +12531,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12555,7 +12558,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -21703,6 +21707,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -21729,7 +21734,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -22122,6 +22128,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -22148,7 +22155,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+12 -4
View File
@@ -13105,6 +13105,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13131,7 +13132,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -13746,6 +13748,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13772,7 +13775,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -30954,6 +30958,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -30980,7 +30985,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -31601,6 +31607,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -31627,7 +31634,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+12 -4
View File
@@ -12127,6 +12127,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12153,7 +12154,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -12529,6 +12531,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12555,7 +12558,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -21703,6 +21707,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -21729,7 +21734,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -22122,6 +22128,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -22148,7 +22155,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+12 -4
View File
@@ -13041,6 +13041,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13067,7 +13068,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -13683,6 +13685,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13709,7 +13712,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -31058,6 +31062,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -31084,7 +31089,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -31708,6 +31714,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -31734,7 +31741,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+12 -4
View File
@@ -12078,6 +12078,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12104,7 +12105,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -12493,6 +12495,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12519,7 +12522,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -21845,6 +21849,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -21871,7 +21876,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -22277,6 +22283,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -22303,7 +22310,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+12 -4
View File
@@ -13041,6 +13041,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13067,7 +13068,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -13683,6 +13685,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -13709,7 +13712,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -31058,6 +31062,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -31084,7 +31089,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -31708,6 +31714,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -31734,7 +31741,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+12 -4
View File
@@ -12078,6 +12078,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12104,7 +12105,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -12493,6 +12495,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -12519,7 +12522,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -21845,6 +21849,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -21871,7 +21876,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
@@ -22277,6 +22283,7 @@
"dart-3.3",
"dart-3.5",
"dart-3.8",
"dart-3.9",
"dotnet-6.0",
"dotnet-7.0",
"dotnet-8.0",
@@ -22303,7 +22310,8 @@
"flutter-3.24",
"flutter-3.27",
"flutter-3.29",
"flutter-3.32"
"flutter-3.32",
"flutter-3.35"
],
"x-enum-name": null,
"x-enum-keys": []
+2 -2
View File
@@ -14,7 +14,7 @@ return [
],
'DART' => [
'name' => 'dart',
'versions' => ['3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
'versions' => ['3.9', '3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
],
'GO' => [
'name' => 'go',
@@ -38,6 +38,6 @@ return [
],
'FLUTTER' => [
'name' => 'flutter',
'versions' => ['3.32', '3.24']
'versions' => ['3.35', '3.32', '3.24']
],
];
+1 -1
View File
@@ -84,7 +84,7 @@ const TEMPLATE_FRAMEWORKS = [
'installCommand' => '',
'buildCommand' => 'flutter build web',
'outputDirectory' => './build/web',
'buildRuntime' => 'flutter-3.29',
'buildRuntime' => 'flutter-3.35',
'adapter' => 'static',
'fallbackFile' => '',
],
+83 -7
View File
@@ -20,7 +20,7 @@ use Appwrite\Event\Messaging;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Email as EmailValidator;
use Appwrite\Network\Validator\Redirect;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\SDK\AuthType;
@@ -57,6 +57,7 @@ use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Locale\Locale;
use Utopia\Storage\Validator\FileName;
use Utopia\System\System;
@@ -337,7 +338,7 @@ App::post('/v1/account')
))
->label('abuse-limit', 10)
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('request')
@@ -394,6 +395,13 @@ App::post('/v1/account')
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
try {
$userId = $userId == 'unique()' ? ID::unique() : $userId;
$user->setAttributes([
@@ -422,7 +430,13 @@ App::post('/v1/account')
'authenticators' => null,
'search' => implode(' ', [$userId, $email, $name]),
'accessedAt' => DateTime::now(),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
]);
$user->removeAttribute('$sequence');
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
try {
@@ -903,7 +917,7 @@ App::post('/v1/account/sessions/email')
))
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('request')
->inject('response')
@@ -1598,6 +1612,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$failureRedirect(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
try {
$userId = ID::unique();
$user->setAttributes([
@@ -1625,7 +1645,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'authenticators' => null,
'search' => implode(' ', [$userId, $email, $name]),
'accessedAt' => DateTime::now(),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
]);
$user->removeAttribute('$sequence');
$userDoc = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$dbForProject->createDocument('targets', new Document([
@@ -1696,6 +1722,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
if (empty($user->getAttribute('email'))) {
$user->setAttribute('email', $oauth2->getUserEmail($accessToken));
try {
$emailCanonical = new Email($user->getAttribute('email'));
} catch (Throwable) {
$emailCanonical = null;
}
$user->setAttribute('emailCanonical', $emailCanonical?->getCanonical());
$user->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported());
$user->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate());
$user->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable());
$user->setAttribute('emailIsFree', $emailCanonical?->isFree());
}
if (empty($user->getAttribute('name'))) {
@@ -1944,7 +1982,7 @@ App::post('/v1/account/tokens/magic-url')
->label('abuse-limit', 60)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the magic URL login. 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.', true, ['platforms', 'devKey'])
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
->inject('request')
@@ -1990,6 +2028,12 @@ App::post('/v1/account/tokens/magic-url')
$userId = $userId === 'unique()' ? ID::unique() : $userId;
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
$user->setAttributes([
'$id' => $userId,
'$permissions' => [
@@ -2014,6 +2058,11 @@ App::post('/v1/account/tokens/magic-url')
'authenticators' => null,
'search' => implode(' ', [$userId, $email]),
'accessedAt' => DateTime::now(),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
]);
$user->removeAttribute('$sequence');
@@ -2197,7 +2246,7 @@ App::post('/v1/account/tokens/email')
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
->inject('request')
->inject('response')
@@ -2240,6 +2289,12 @@ App::post('/v1/account/tokens/email')
$userId = $userId === 'unique()' ? ID::unique() : $userId;
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
$user->setAttributes([
'$id' => $userId,
'$permissions' => [
@@ -2262,6 +2317,11 @@ App::post('/v1/account/tokens/email')
'memberships' => null,
'search' => implode(' ', [$userId, $email]),
'accessedAt' => DateTime::now(),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
]);
$user->removeAttribute('$sequence');
@@ -2609,6 +2669,11 @@ App::post('/v1/account/tokens/phone')
'memberships' => null,
'search' => implode(' ', [$userId, $phone]),
'accessedAt' => DateTime::now(),
'emailCanonical' => null,
'emailIsCanonical' => null,
'emailIsCorporate' => null,
'emailIsDisposable' => null,
'emailIsFree' => null,
]);
$user->removeAttribute('$sequence');
@@ -3037,7 +3102,7 @@ App::patch('/v1/account/email')
],
contentType: ContentType::JSON
))
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('requestTimestamp')
->inject('response')
@@ -3072,9 +3137,20 @@ App::patch('/v1/account/email')
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
->setAttribute('emailCanonical', $emailCanonical?->getCanonical())
->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported())
->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate())
->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable())
->setAttribute('emailIsFree', $emailCanonical?->isFree())
;
if (empty($passwordUpdate)) {
@@ -3311,7 +3387,7 @@ App::post('/v1/account/recovery')
))
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey'])
->inject('request')
->inject('response')
+49 -34
View File
@@ -10,7 +10,7 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Email as EmailValidator;
use Appwrite\Network\Validator\Redirect;
use Appwrite\Platform\Workers\Deletes;
use Appwrite\SDK\AuthType;
@@ -48,6 +48,7 @@ use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -468,7 +469,7 @@ App::post('/v1/teams/:teamId/memberships')
))
->label('abuse-limit', 10)
->param('teamId', '', new UID(), 'Team ID.')
->param('email', '', new Email(), 'Email of the new team member.', true)
->param('email', '', new EmailValidator(), 'Email of the new team member.', true)
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], function (Document $project) {
@@ -567,38 +568,52 @@ App::post('/v1/teams/:teamId/memberships')
}
try {
$userId = ID::unique();
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$permissions' => [
Permission::read(Role::any()),
Permission::read(Role::user($userId)),
Permission::update(Role::user($userId)),
Permission::delete(Role::user($userId)),
],
'email' => empty($email) ? null : $email,
'phone' => empty($phone) ? null : $phone,
'emailVerification' => false,
'status' => true,
// TODO: Set password empty?
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
* old password
*/
'passwordUpdate' => null,
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
])));
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
$userId = ID::unique();
$userDocument = new Document([
'$id' => $userId,
'$permissions' => [
Permission::read(Role::any()),
Permission::read(Role::user($userId)),
Permission::update(Role::user($userId)),
Permission::delete(Role::user($userId)),
],
'email' => empty($email) ? null : $email,
'phone' => empty($phone) ? null : $phone,
'emailVerification' => false,
'status' => true,
// TODO: Set password empty?
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
* old password
*/
'passwordUpdate' => null,
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
]);
try {
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', $userDocument));
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
+35 -12
View File
@@ -16,7 +16,7 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Email as EmailValidator;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -49,6 +49,7 @@ use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -97,6 +98,12 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
}
}
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$user = new Document([
'$id' => $userId,
@@ -124,6 +131,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $phone, $name]),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
]);
if ($hash === 'plaintext') {
@@ -208,7 +220,7 @@ App::post('/v1/users')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', null, new Email(), 'User email.', true)
->param('email', null, new EmailValidator(), 'User email.', true)
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -243,7 +255,7 @@ App::post('/v1/users/bcrypt')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
@@ -278,7 +290,7 @@ App::post('/v1/users/md5')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using MD5.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
@@ -313,7 +325,7 @@ App::post('/v1/users/argon2')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Argon2.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
@@ -348,7 +360,7 @@ App::post('/v1/users/sha')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using SHA.')
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -390,7 +402,7 @@ App::post('/v1/users/phpass')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. 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('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using PHPass.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
@@ -425,7 +437,7 @@ App::post('/v1/users/scrypt')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt.')
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
->param('passwordCpu', 8, new Integer(), 'Optional CPU cost used to hash password.')
@@ -473,7 +485,7 @@ App::post('/v1/users/scrypt-modified')
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt Modified.')
->param('passwordSalt', '', new Text(128), 'Salt used to hash password.')
->param('passwordSaltSeparator', '', new Text(128), 'Salt separator used to hash password.')
@@ -527,7 +539,7 @@ App::post('/v1/users/:userId/targets')
switch ($providerType) {
case 'email':
$validator = new Email();
$validator = new EmailValidator();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
@@ -1402,7 +1414,7 @@ App::patch('/v1/users/:userId/email')
]
))
->param('userId', '', new UID(), 'User ID.')
->param('email', '', new Email(allowEmpty: true), 'User email.')
->param('email', '', new EmailValidator(allowEmpty: true), 'User email.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -1437,9 +1449,20 @@ App::patch('/v1/users/:userId/email')
$oldEmail = $user->getAttribute('email');
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false)
->setAttribute('emailCanonical', $emailCanonical?->getCanonical())
->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported())
->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate())
->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable())
->setAttribute('emailIsFree', $emailCanonical?->isFree())
;
try {
@@ -1700,7 +1723,7 @@ App::patch('/v1/users/:userId/targets/:targetId')
switch ($providerType) {
case 'email':
$validator = new Email();
$validator = new EmailValidator();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
Generated
+24 -24
View File
@@ -161,16 +161,16 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.19.1",
"version": "0.19.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "7bd0cc3cb97de625d7b07230bd91b121f88e72ae"
"reference": "e5c142519df5aced37de9c302971c29c079ce3d9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/7bd0cc3cb97de625d7b07230bd91b121f88e72ae",
"reference": "7bd0cc3cb97de625d7b07230bd91b121f88e72ae",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/e5c142519df5aced37de9c302971c29c079ce3d9",
"reference": "e5c142519df5aced37de9c302971c29c079ce3d9",
"shasum": ""
},
"require": {
@@ -210,9 +210,9 @@
],
"support": {
"issues": "https://github.com/appwrite/runtimes/issues",
"source": "https://github.com/appwrite/runtimes/tree/0.19.1"
"source": "https://github.com/appwrite/runtimes/tree/0.19.2"
},
"time": "2025-05-27T07:12:56+00:00"
"time": "2025-11-11T13:44:44+00:00"
},
{
"name": "beberlei/assert",
@@ -756,16 +756,16 @@
},
{
"name": "google/protobuf",
"version": "v4.33.0",
"version": "v4.33.1",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d"
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d",
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
"shasum": ""
},
"require": {
@@ -794,9 +794,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.1"
},
"time": "2025-10-15T20:10:28+00:00"
"time": "2025-11-12T21:58:05+00:00"
},
{
"name": "league/csv",
@@ -3844,16 +3844,16 @@
},
{
"name": "utopia-php/database",
"version": "3.2.0",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "f2d01b6b38057891184f62107bf70a55bc2ea068"
"reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/f2d01b6b38057891184f62107bf70a55bc2ea068",
"reference": "f2d01b6b38057891184f62107bf70a55bc2ea068",
"url": "https://api.github.com/repos/utopia-php/database/zipball/e10b4faa4f3a3ef30a5f6d76acdb605469924aec",
"reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec",
"shasum": ""
},
"require": {
@@ -3896,9 +3896,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/3.2.0"
"source": "https://github.com/utopia-php/database/tree/3.4.0"
},
"time": "2025-11-06T05:41:54+00:00"
"time": "2025-11-13T06:34:20+00:00"
},
{
"name": "utopia-php/detector",
@@ -5383,16 +5383,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.5.3",
"version": "1.5.4",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "1a7a3b89147aa8c1bde5247f8eeb7e4832c6016d"
"reference": "958947b6483a79e11c3812f23bb3056199fa4105"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/1a7a3b89147aa8c1bde5247f8eeb7e4832c6016d",
"reference": "1a7a3b89147aa8c1bde5247f8eeb7e4832c6016d",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/958947b6483a79e11c3812f23bb3056199fa4105",
"reference": "958947b6483a79e11c3812f23bb3056199fa4105",
"shasum": ""
},
"require": {
@@ -5428,9 +5428,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.5.3"
"source": "https://github.com/appwrite/sdk-generator/tree/1.5.4"
},
"time": "2025-11-10T09:50:41+00:00"
"time": "2025-11-12T12:43:42+00:00"
},
{
"name": "doctrine/annotations",
@@ -0,0 +1,41 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Avatars;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
Avatars avatars = new Avatars(client);
avatars.getScreenshot(
"https://example.com", // url
mapOf( "a" to "b" ), // headers (optional)
1, // viewportWidth (optional)
1, // viewportHeight (optional)
0.1, // scale (optional)
theme.LIGHT, // theme (optional)
"<USER_AGENT>", // userAgent (optional)
false, // fullpage (optional)
"<LOCALE>", // locale (optional)
timezone.AFRICA_ABIDJAN, // timezone (optional)
-90, // latitude (optional)
-180, // longitude (optional)
0, // accuracy (optional)
false, // touch (optional)
listOf(), // permissions (optional)
0, // sleep (optional)
0, // width (optional)
0, // height (optional)
-1, // quality (optional)
output.JPG, // output (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
@@ -0,0 +1,32 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Avatars
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
val avatars = Avatars(client)
val result = avatars.getScreenshot(
url = "https://example.com",
headers = mapOf( "a" to "b" ), // (optional)
viewportWidth = 1, // (optional)
viewportHeight = 1, // (optional)
scale = 0.1, // (optional)
theme = theme.LIGHT, // (optional)
userAgent = "<USER_AGENT>", // (optional)
fullpage = false, // (optional)
locale = "<LOCALE>", // (optional)
timezone = timezone.AFRICA_ABIDJAN, // (optional)
latitude = -90, // (optional)
longitude = -180, // (optional)
accuracy = 0, // (optional)
touch = false, // (optional)
permissions = listOf(), // (optional)
sleep = 0, // (optional)
width = 0, // (optional)
height = 0, // (optional)
quality = -1, // (optional)
output = output.JPG, // (optional)
)
@@ -0,0 +1,32 @@
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
let avatars = Avatars(client)
let bytes = try await avatars.getScreenshot(
url: "https://example.com",
headers: [:], // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .light, // optional
userAgent: "<USER_AGENT>", // optional
fullpage: false, // optional
locale: "<LOCALE>", // optional
timezone: .africaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .jpg // optional
)
@@ -0,0 +1,65 @@
import 'package:appwrite/appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
Avatars avatars = Avatars(client);
// Downloading file
UInt8List bytes = await avatars.getScreenshot(
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .africaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .jpg, // optional
)
final file = File('path_to_file/filename.ext');
file.writeAsBytesSync(bytes);
// Displaying image preview
FutureBuilder(
future: avatars.getScreenshot(
url:'https://example.com' ,
headers:{} , // optional
viewportWidth:1 , // optional
viewportHeight:1 , // optional
scale:0.1 , // optional
theme: .light, // optional
userAgent:'<USER_AGENT>' , // optional
fullpage:false , // optional
locale:'<LOCALE>' , // optional
timezone: .africaAbidjan, // optional
latitude:-90 , // optional
longitude:-180 , // optional
accuracy:0 , // optional
touch:false , // optional
permissions:[] , // optional
sleep:0 , // optional
width:0 , // optional
height:0 , // optional
quality:-1 , // optional
output: .jpg, // optional
), // Works for both public file and private file, for private files you need to be logged in
builder: (context, snapshot) {
return snapshot.hasData && snapshot.data != null
? Image.memory(snapshot.data)
: CircularProgressIndicator();
}
);
@@ -0,0 +1,32 @@
import { Client, Avatars, , , } from "react-native-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
const avatars = new Avatars(client);
const result = avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
});
console.log(result);
@@ -0,0 +1,6 @@
GET /v1/avatars/screenshots HTTP/1.1
Host: cloud.appwrite.io
X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
@@ -0,0 +1,32 @@
import { Client, Avatars, , , } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
const avatars = new Avatars(client);
const result = avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
});
console.log(result);
@@ -0,0 +1,32 @@
import { Client, Avatars, , , } from "@appwrite.io/console";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
const avatars = new Avatars(client);
const result = avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
});
console.log(result);
@@ -0,0 +1,31 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
Avatars avatars = Avatars(client);
UInt8List result = await avatars.getScreenshot(
url: 'https://example.com',
headers: {}, // (optional)
viewportWidth: 1, // (optional)
viewportHeight: 1, // (optional)
scale: 0.1, // (optional)
theme: .light, // (optional)
userAgent: '<USER_AGENT>', // (optional)
fullpage: false, // (optional)
locale: '<LOCALE>', // (optional)
timezone: .africaAbidjan, // (optional)
latitude: -90, // (optional)
longitude: -180, // (optional)
accuracy: 0, // (optional)
touch: false, // (optional)
permissions: [], // (optional)
sleep: 0, // (optional)
width: 0, // (optional)
height: 0, // (optional)
quality: -1, // (optional)
output: .jpg, // (optional)
);
@@ -0,0 +1,34 @@
using Appwrite;
using Appwrite.Enums;
using Appwrite.Models;
using Appwrite.Services;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.SetProject("<YOUR_PROJECT_ID>") // Your project ID
.SetSession(""); // The user session to authenticate with
Avatars avatars = new Avatars(client);
byte[] result = await avatars.GetScreenshot(
url: "https://example.com",
headers: [object], // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .Light, // optional
userAgent: "<USER_AGENT>", // optional
fullpage: false, // optional
locale: "<LOCALE>", // optional
timezone: .AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: new List<string>(), // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .Jpg // optional
);
@@ -0,0 +1,38 @@
package main
import (
"fmt"
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/avatars"
)
client := client.New(
client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1")
client.WithProject("<YOUR_PROJECT_ID>")
client.WithSession("")
)
service := avatars.New(client)
response, error := service.GetScreenshot(
"https://example.com",
avatars.WithGetScreenshotHeaders(map[string]interface{}{}),
avatars.WithGetScreenshotViewportWidth(1),
avatars.WithGetScreenshotViewportHeight(1),
avatars.WithGetScreenshotScale(0.1),
avatars.WithGetScreenshotTheme("light"),
avatars.WithGetScreenshotUserAgent("<USER_AGENT>"),
avatars.WithGetScreenshotFullpage(false),
avatars.WithGetScreenshotLocale("<LOCALE>"),
avatars.WithGetScreenshotTimezone("africa/abidjan"),
avatars.WithGetScreenshotLatitude(-90),
avatars.WithGetScreenshotLongitude(-180),
avatars.WithGetScreenshotAccuracy(0),
avatars.WithGetScreenshotTouch(false),
avatars.WithGetScreenshotPermissions([]interface{}{}),
avatars.WithGetScreenshotSleep(0),
avatars.WithGetScreenshotWidth(0),
avatars.WithGetScreenshotHeight(0),
avatars.WithGetScreenshotQuality(-1),
avatars.WithGetScreenshotOutput("jpg"),
)
@@ -0,0 +1,42 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Avatars;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession(""); // The user session to authenticate with
Avatars avatars = new Avatars(client);
avatars.getScreenshot(
"https://example.com", // url
mapOf( "a" to "b" ), // headers (optional)
1, // viewportWidth (optional)
1, // viewportHeight (optional)
0.1, // scale (optional)
.LIGHT, // theme (optional)
"<USER_AGENT>", // userAgent (optional)
false, // fullpage (optional)
"<LOCALE>", // locale (optional)
.AFRICA_ABIDJAN, // timezone (optional)
-90, // latitude (optional)
-180, // longitude (optional)
0, // accuracy (optional)
false, // touch (optional)
listOf(), // permissions (optional)
0, // sleep (optional)
0, // width (optional)
0, // height (optional)
-1, // quality (optional)
.JPG, // output (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);
@@ -0,0 +1,33 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Avatars
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession("") // The user session to authenticate with
val avatars = Avatars(client)
val result = avatars.getScreenshot(
url = "https://example.com",
headers = mapOf( "a" to "b" ), // optional
viewportWidth = 1, // optional
viewportHeight = 1, // optional
scale = 0.1, // optional
theme = "light", // optional
userAgent = "<USER_AGENT>", // optional
fullpage = false, // optional
locale = "<LOCALE>", // optional
timezone = "africa/abidjan", // optional
latitude = -90, // optional
longitude = -180, // optional
accuracy = 0, // optional
touch = false, // optional
permissions = listOf(), // optional
sleep = 0, // optional
width = 0, // optional
height = 0, // optional
quality = -1, // optional
output = "jpg" // optional
)
@@ -0,0 +1,31 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
const avatars = new sdk.Avatars(client);
const result = await avatars.getScreenshot({
url: 'https://example.com',
headers: {}, // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: sdk..Light, // optional
userAgent: '<USER_AGENT>', // optional
fullpage: false, // optional
locale: '<LOCALE>', // optional
timezone: sdk..AfricaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: sdk..Jpg // optional
});
@@ -0,0 +1,32 @@
from appwrite.client import Client
from appwrite.services.avatars import Avatars
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_session('') # The user session to authenticate with
avatars = Avatars(client)
result = avatars.get_screenshot(
url = 'https://example.com',
headers = {}, # optional
viewport_width = 1, # optional
viewport_height = 1, # optional
scale = 0.1, # optional
theme = .LIGHT, # optional
user_agent = '<USER_AGENT>', # optional
fullpage = False, # optional
locale = '<LOCALE>', # optional
timezone = .AFRICA_ABIDJAN, # optional
latitude = -90, # optional
longitude = -180, # optional
accuracy = 0, # optional
touch = False, # optional
permissions = [], # optional
sleep = 0, # optional
width = 0, # optional
height = 0, # optional
quality = -1, # optional
output = .JPG # optional
)
@@ -0,0 +1,7 @@
GET /v1/avatars/screenshots HTTP/1.1
Host: cloud.appwrite.io
X-Appwrite-Response-Format: 1.8.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-Key: <YOUR_API_KEY>
X-Appwrite-JWT: <YOUR_JWT>
@@ -0,0 +1,33 @@
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<YOUR_PROJECT_ID>') # Your project ID
.set_session('') # The user session to authenticate with
avatars = Avatars.new(client)
result = avatars.get_screenshot(
url: 'https://example.com',
headers: {}, # optional
viewport_width: 1, # optional
viewport_height: 1, # optional
scale: 0.1, # optional
theme: ::LIGHT, # optional
user_agent: '<USER_AGENT>', # optional
fullpage: false, # optional
locale: '<LOCALE>', # optional
timezone: ::AFRICA_ABIDJAN, # optional
latitude: -90, # optional
longitude: -180, # optional
accuracy: 0, # optional
touch: false, # optional
permissions: [], # optional
sleep: 0, # optional
width: 0, # optional
height: 0, # optional
quality: -1, # optional
output: ::JPG # optional
)
@@ -0,0 +1,33 @@
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession("") // The user session to authenticate with
let avatars = Avatars(client)
let bytes = try await avatars.getScreenshot(
url: "https://example.com",
headers: [:], // optional
viewportWidth: 1, // optional
viewportHeight: 1, // optional
scale: 0.1, // optional
theme: .light, // optional
userAgent: "<USER_AGENT>", // optional
fullpage: false, // optional
locale: "<LOCALE>", // optional
timezone: .africaAbidjan, // optional
latitude: -90, // optional
longitude: -180, // optional
accuracy: 0, // optional
touch: false, // optional
permissions: [], // optional
sleep: 0, // optional
width: 0, // optional
height: 0, // optional
quality: -1, // optional
output: .jpg // optional
)
+7
View File
@@ -1,5 +1,12 @@
# Change Log
## 19.4.0
* Add `getScreenshot` method to `Avatars` service
* Add enums `Theme`, `Output` and `Timezone`
* Update runtime enums to add support for `dart39` and `flutter335` runtimes
* Fix passing of `null` values and stripping only non-nullable optional parameters from the request body
## 19.3.0
* Add `total` parameter to list queries allowing skipping counting rows in a table for improved performance
+4
View File
@@ -1,5 +1,9 @@
# Change Log
## 20.3.1
* Fix passing of `null` values and stripping only non-nullable optional parameters from the request body
## 20.3.0
* Add `total` parameter to list queries allowing skipping counting rows in a table for improved performance
+50
View File
@@ -6,6 +6,7 @@ use Appwrite\Migration\Migration;
use Exception;
use Throwable;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Conflict;
@@ -132,6 +133,13 @@ class V23 extends Migration
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'migrations':
try {
$this->updateMigrateErrorSize();
} catch (\Throwable $th) {
Console::warning("Failed to migration error attribute size in collection {$id}: {$th->getMessage()}");
}
default:
break;
}
@@ -201,4 +209,46 @@ class V23 extends Migration
}
return $document;
}
/**
* Update migration attribute size
* @return void
*/
private function updateMigrateErrorSize(): void
{
if ($this->project->getId() === 'console') {
return;
}
// Read-modify-write from the live schema to avoid overwriting unrelated changes.
$migration = $this->dbForProject->getCollection('migrations');
$attributes = $migration->getAttribute('attributes', []);
$attrsArray = \array_map(fn (Document $doc) => $doc->getArrayCopy(), $attributes);
$errorsIdx = \array_search('errors', \array_column($attrsArray, '$id'));
if ($errorsIdx === false) {
Console::warning("Skipping: 'errors' attribute not found in migrations collection for project {$this->project->getId()}");
return;
}
$desiredSize = 1_000_000;
$migrationAttributes = Config::getParam('collections', [])['projects']['migrations']['attributes'] ?? [];
$migrationIndex = \array_search('errors', \array_column($migrationAttributes, '$id'));
if ($migrationIndex !== false && isset($migrationAttributes[$migrationIndex]['size'])) {
$desiredSize = (int) $migrationAttributes[$migrationIndex]['size'];
}
$currentSize = (int) ($attributes[$errorsIdx]['size'] ?? 0);
if ($currentSize === $desiredSize) {
Console::warning("Skipping: 'errors' attribute already of desired size {$desiredSize} in migrations collection for project {$this->project->getId()}");
return;
}
$attributes[$errorsIdx]['size'] = $desiredSize;
$migration->setAttribute('attributes', $attributes);
$this->dbForProject->updateDocument($migration->getCollection(), $migration->getId(), $migration);
$this->dbForProject->purgeCachedCollection('migrations');
}
}
@@ -255,10 +255,21 @@ class OpenAPI3 extends Format
}
foreach ($methodObj->getResponses() as $response) {
if (\is_array($response->getModel())) {
/** @var Response|array $response */
$responseModel = $response->getModel();
if (\is_array($responseModel)) {
foreach ($responseModel as $modelName) {
foreach ($this->models as $value) {
if ($value->getType() === $modelName) {
$usedModels[] = $modelName;
break;
}
}
}
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => \array_map(fn ($m) => '#/components/schemas/' . $m, $response->getModel())
'model' => \array_map(fn ($m) => '#/components/schemas/' . $m, $responseModel)
];
} else {
$responseData = [
@@ -267,7 +278,13 @@ class OpenAPI3 extends Format
// lets not assume stuff here!
if ($response->getCode() !== 204) {
$responseData['model'] = '#/components/schemas/' . $response->getModel();
$responseData['model'] = '#/components/schemas/' . $responseModel;
foreach ($this->models as $value) {
if ($value->getType() === $responseModel) {
$usedModels[] = $responseModel;
break;
}
}
}
$additionalMethod['responses'][] = $responseData;
@@ -264,11 +264,20 @@ class Swagger2 extends Format
}
foreach ($methodObj->getResponses() as $response) {
/** @var Response $response */
if (\is_array($response->getModel())) {
/** @var Response|array $response */
$responseModel = $response->getModel();
if (\is_array($responseModel)) {
foreach ($responseModel as $modelName) {
foreach ($this->models as $value) {
if ($value->getType() === $modelName) {
$usedModels[] = $modelName;
break;
}
}
}
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => \array_map(fn ($m) => '#/definitions/' . $m, $response->getModel())
'model' => \array_map(fn ($m) => '#/definitions/' . $m, $responseModel)
];
} else {
$responseData = [
@@ -277,7 +286,13 @@ class Swagger2 extends Format
// lets not assume stuff here!
if ($response->getCode() !== 204) {
$responseData['model'] = '#/definitions/' . $response->getModel();
$responseData['model'] = '#/definitions/' . $responseModel;
foreach ($this->models as $value) {
if ($value->getType() === $responseModel) {
$usedModels[] = $responseModel;
break;
}
}
}
$additionalMethod['responses'][] = $responseData;
@@ -41,6 +41,11 @@ trait AccountBase
$this->assertNotEmpty($response['body']['accessedAt']);
$this->assertArrayHasKey('targets', $response['body']);
$this->assertEquals($email, $response['body']['targets'][0]['identifier']);
$this->assertArrayNotHasKey('emailCanonical', $response['body']);
$this->assertArrayNotHasKey('emailIsFree', $response['body']);
$this->assertArrayNotHasKey('emailIsDisposable', $response['body']);
$this->assertArrayNotHasKey('emailIsCorporate', $response['body']);
$this->assertArrayNotHasKey('emailIsCanonical', $response['body']);
/**
* Test for FAILURE