Compare commits

...
625 Commits
Author SHA1 Message Date
HemachandarandGitHub 43f2cd0599 Merge branch '1.8.x' into fix-mails 2026-01-30 07:28:38 +05:30
Jake BarnbyandGitHub 6ccc8b9261 Merge pull request #11174 from appwrite/feat-string-types 2026-01-29 14:39:06 +00:00
Jake Barnby 0d79ab0fd3 Update specs 2026-01-30 02:21:32 +13:00
Jake Barnby c9aac86a6b Merge remote-tracking branch 'origin/1.8.x' into feat-string-types
# Conflicts:
#	composer.lock
2026-01-30 02:07:33 +13:00
Matej BačoandGitHub 8c626ea2ca Merge pull request #11206 from appwrite/chore-upgrade-templates
Chore: Upgrade templates version
2026-01-29 13:53:32 +01:00
Jake BarnbyandGitHub 9d953f898d Merge pull request #11007 from appwrite/new-pool-adapter 2026-01-29 12:44:13 +00:00
Matej BačoandGitHub c466ffcf7e Merge branch '1.8.x' into chore-upgrade-templates 2026-01-29 12:54:58 +01:00
Matej BačoandGitHub 366e578cf8 Merge pull request #11189 from appwrite/chore-usecases-enums
Chore: Proper quality to template usecases enums
2026-01-29 12:49:38 +01:00
Matej Bačo 63ceea7aca Fix wrong ref versions 2026-01-29 12:30:47 +01:00
Matej BačoandGitHub 1df09c652d Merge branch '1.8.x' into chore-usecases-enums 2026-01-29 12:12:17 +01:00
ArnabChatterjee20k 6b9f48aa76 updated lock 2026-01-29 16:32:49 +05:30
ArnabChatterjee20k c8c5fbaca2 updated lock 2026-01-29 16:18:23 +05:30
Matej Bačo 1131e4ed18 Revert "Upgrade template versions"
This reverts commit a2d920195d.
2026-01-29 11:46:05 +01:00
Matej Bačo a2d920195d Upgrade template versions 2026-01-29 11:39:42 +01:00
ArnabChatterjee20k 5753c7694d updated lock 2026-01-29 14:52:16 +05:30
ArnabChatterjee20k 0d057390ab updated composer and docker compose 2026-01-29 14:34:20 +05:30
ArnabChatterjee20k e82345be40 updated tests 2026-01-29 14:22:08 +05:30
ArnabChatterjee20kandGitHub 7973824321 Merge branch '1.8.x' into new-pool-adapter 2026-01-29 11:20:02 +05:30
Jake BarnbyandGitHub b1ca73a041 Merge pull request #11193 from premtsd-code/fix-10612-validate-relationship-document-id 2026-01-29 04:56:47 +00:00
premtsd-codeandGitHub 845aa5c363 Merge branch '1.8.x' into fix-10612-validate-relationship-document-id 2026-01-28 18:45:53 +00:00
Prem Palanisamy 23dae85d2f Sync composer.lock with 1.8.x 2026-01-28 18:45:11 +00:00
Luke B. SilverandGitHub 7bf02811f2 Merge pull request #11198 from appwrite/refactor-remove-magic-class-strings
refactor: remove magic class strings
2026-01-28 16:56:31 +00:00
premtsd-codeandGitHub 23c1f8fd5a Merge branch '1.8.x' into fix-10612-validate-relationship-document-id 2026-01-28 15:21:40 +00:00
Matej BačoandGitHub b9cb81496d Merge pull request #11203 from appwrite/chore-deprecate-project-attributes
Chore: Remove unnessessary attributes
2026-01-28 15:58:58 +01:00
Matej Bačo e22e8d6a5f Upgrade phpunit for vuln 2026-01-28 14:55:13 +01:00
ArnabChatterjee20kandGitHub 24385355a0 Merge branch '1.8.x' into new-pool-adapter 2026-01-28 19:24:59 +05:30
ArnabChatterjee20k 44b52b5a00 updated swoole and pools 2026-01-28 19:24:33 +05:30
ArnabChatterjee20k 6c29dd055c updated php unit 2026-01-28 19:21:01 +05:30
Matej Bačo 14a96a2b56 Remove unnessessary attributes 2026-01-28 14:50:17 +01:00
Matej BačoandGitHub 273364114a Merge pull request #10988 from appwrite/chore-php-types
Feat: CE support for Platform API
2026-01-28 14:45:42 +01:00
Prem Palanisamy 2f3fa9e0d3 sync CONTRIBUTING.md with 1.8.x 2026-01-28 11:42:51 +00:00
premtsd-codeandGitHub 71670f8169 Merge branch '1.8.x' into fix-10612-validate-relationship-document-id 2026-01-28 11:17:50 +00:00
Matej BačoandGitHub a4a589c535 Merge branch '1.8.x' into chore-php-types 2026-01-28 11:44:51 +01:00
Prem Palanisamy cbe2d2383d chore: update phpunit to 9.6.34 (security fix) 2026-01-28 10:24:11 +00:00
Prem Palanisamy aef7b8df38 fix: use RELATIONSHIP_VALUE_INVALID exception for validation errors 2026-01-28 08:41:10 +00:00
ArnabChatterjee20k 125394166a updated docker appwrite image base 2026-01-28 12:04:26 +05:30
ArnabChatterjee20k 68cb03d22c updated to swoole 6 2026-01-27 19:16:57 +05:30
Prem Palanisamy 7f3ea98924 refactor: use array_is_list() and assertEventually helper 2026-01-27 13:00:29 +00:00
loks0n cb66e50612 refactor: remove magic class strings 2026-01-27 11:58:34 +00:00
ArnabChatterjee20k 87db62b018 updated env 2026-01-27 16:39:08 +05:30
ArnabChatterjee20k 5fe5445283 Merge remote-tracking branch 'origin/1.8.x' into new-pool-adapter 2026-01-27 16:37:58 +05:30
ArnabChatterjee20k 303fca0fd5 updated pools 2026-01-27 16:36:28 +05:30
HemachandarandGitHub 99dc31062d Fix rule status check (#11195) 2026-01-27 15:36:50 +05:30
Prem Palanisamy d182c85330 fix: reject unsupported relationship value types 2026-01-27 09:35:45 +00:00
Prem Palanisamy d792d3bbea refactor: use getId() instead of getAttribute('$id') 2026-01-27 09:25:39 +00:00
Prem Palanisamy 00d091513d refactor: simplify relationship validation code 2026-01-27 06:59:53 +00:00
Prem Palanisamy 63e6a51af1 test: add assertion for relationship column polling 2026-01-26 15:45:42 +00:00
Prem Palanisamy 1ee2539ce0 fix: generate unique ID before validation per coderabbit suggestion 2026-01-26 15:45:42 +00:00
premtsd-codeandGitHub cb32dc40ec Merge branch 'main' into fix-10612-validate-relationship-document-id 2026-01-26 14:06:44 +00:00
Prem Palanisamy f66e0c2ff5 refactor: separate validation from normalization in validateRelationship 2026-01-26 14:05:56 +00:00
Prem Palanisamy aed9816d1e fix: validate relationship document ID 2026-01-26 13:48:32 +00:00
Chirag AggarwalandGitHub 6514a992a1 Merge pull request #11078 from appwrite/feat-sdk-for-md 2026-01-26 13:06:10 +05:30
Chirag Aggarwal 1167654c6a Merge branch '1.8.x' into feat-sdk-for-md 2026-01-26 13:03:02 +05:30
Chirag AggarwalandGitHub 28c5df074d Merge pull request #11169 from appwrite/release-cli-13.1.0 2026-01-26 12:59:35 +05:30
Chirag Aggarwal 785efa3355 stable release 2026-01-26 10:40:29 +05:30
Chirag Aggarwal 329b7e1856 Merge branch '1.8.x' into feat-sdk-for-md 2026-01-26 10:25:28 +05:30
Matej Bačo 752731050e Proper quality to template usecases enums 2026-01-25 21:55:52 +01:00
HemachandarandGitHub 555915e0c0 Merge branch '1.8.x' into fix-mails 2026-01-26 01:17:12 +05:30
Hemachandar 89b89384f3 Update template 2026-01-26 01:11:14 +05:30
Jake Barnby 368113ade5 Merge branch 'feat-string-types' of github.com:appwrite/appwrite into feat-string-types
# Conflicts:
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php
2026-01-24 16:24:35 +13:00
Jake Barnby f1d1c5a737 Merge remote-tracking branch 'origin/1.8.x' into feat-string-types
# Conflicts:
#	composer.lock
2026-01-24 16:23:39 +13:00
Jake Barnby 5815008994 Update deprecation 2026-01-24 16:23:05 +13:00
Jake BarnbyandGitHub ac83d417aa Merge pull request #11183 from appwrite/copilot/sub-pr-11174 2026-01-24 14:47:34 +13:00
copilot-swe-agent[bot]andabnegate 9993c15fb8 chore: revert String attribute routes deprecation to 1.8.0
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 01:15:15 +00:00
copilot-swe-agent[bot]andabnegate 11aecdf33f chore: mark TablesDB String column routes as deprecated
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 01:11:03 +00:00
copilot-swe-agent[bot]andabnegate a088e85a24 chore: update String attribute deprecation to 1.9.0
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 01:01:49 +00:00
copilot-swe-agent[bot]andabnegate 1fd687b6b7 chore: update composer.lock after reverting to 4.*
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 00:30:56 +00:00
copilot-swe-agent[bot]andabnegate aa26823549 chore: revert utopia-php/database to 4.*
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 00:26:11 +00:00
copilot-swe-agent[bot] 109e16362d Initial plan 2026-01-24 00:23:12 +00:00
Jake BarnbyandGitHub 294518d014 Merge branch '1.8.x' into feat-string-types 2026-01-24 13:21:30 +13:00
Jake BarnbyandClaude Opus 4.5 046a7cd90e test: skip update tests pending utopia-php/database fix
The updateAttribute method in utopia-php/database does not yet support
VARCHAR, TEXT, MEDIUMTEXT, and LONGTEXT types. These tests are skipped
until the upstream library adds support for updating these attribute types.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 06:15:21 +13:00
Jake BarnbyandClaude Opus 4.5 fea210c9ed chore: use utopia-php/database dev-feat-string-types branch
The string types feature requires the dev-feat-string-types branch of
utopia-php/database which includes support for VARCHAR, TEXT, MEDIUMTEXT,
and LONGTEXT types in the updateAttribute method.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 05:51:28 +13:00
Jake BarnbyandClaude Opus 4.5 760f065711 fix: reduce longtext size to fit within INT column limit
The size field in the attributes collection is stored as a signed 32-bit
integer (VAR_INTEGER). The longtext size of 4294967295 (2^32-1) exceeds
the maximum value of 2147483647 (2^31-1), causing attribute creation to
fail with a 400 error (document_invalid_structure).

Changed the longtext size to 2147483647 which is the maximum value that
fits within the signed 32-bit integer constraint of the schema.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 05:24:26 +13:00
Jake BarnbyandClaude Opus 4.5 c42fbc49f5 fix: remove max-size varchar test that exceeds row width limit
The test tried to create multiple VARCHAR attributes in the same
collection, including a 16381-character varchar. The cumulative
row width exceeded MariaDB's 65535 byte row limit, causing the
test to fail with a 400 error.

Calculation: 1067 (base) + 1021 (255*4+1) + 401 (100*4+1) + 201
(50*4+1) + 20 (array) + 65526 (16381*4+2) = 68236 bytes > 65535

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 04:25:06 +13:00
Chirag AggarwalandGitHub 0dd2f29a7e Merge pull request #11182 from appwrite/chore-make-update-name-optional 2026-01-23 20:51:36 +05:30
Jake BarnbyandClaude Opus 4.5 e20851e7a8 fix: reorder imports to satisfy linter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 03:53:42 +13:00
Chirag Aggarwal 67e2104e98 chore: make name update optional in databases and tables 2026-01-23 19:55:06 +05:30
Hemachandar 05beefe155 Merge branch '1.8.x' into fix-mails 2026-01-23 16:34:30 +05:30
HemachandarandGitHub a2ff833efe Upgrade utopia-php/domains (#11181)
* Upgrade utopia-php/domains

* update
2026-01-23 15:31:20 +05:30
Chirag Aggarwal e894eca201 add rc3 2026-01-23 15:25:05 +05:30
Chirag AggarwalandGitHub 7573e0fee9 Merge branch '1.8.x' into release-cli-13.1.0 2026-01-23 13:42:30 +05:30
Chirag AggarwalandGitHub 46be2d0e49 Merge pull request #11179 from appwrite/fix-deployment-enum 2026-01-23 11:03:54 +05:30
Chirag AggarwalandGitHub 700ed9f882 Merge pull request #11180 from appwrite/chore-update-executor-healthcheck 2026-01-23 11:03:41 +05:30
Chirag Aggarwal 440be52405 chore: update executor healthcheck to improve flakiness 2026-01-23 09:47:54 +05:30
Chirag Aggarwal 7643196d97 fix: deploymentstatus enum missing canceled value 2026-01-23 09:22:18 +05:30
Chirag Aggarwal 7e994f8db5 release cli sdk 13.1.0 2026-01-23 09:15:13 +05:30
Jake Barnby 1b7cc0b5f9 Add docs 2026-01-23 01:51:58 +13:00
Jake Barnby 60a0b663b9 Add tests 2026-01-23 01:51:52 +13:00
Jake Barnby e6496ec453 Add routes 2026-01-23 01:51:43 +13:00
Jake Barnby 07204d748d Add response models 2026-01-23 01:51:24 +13:00
Matej Bačo b317f85fb6 Fix depricated schema 2026-01-21 16:27:09 +01:00
Matej Bačo 8e98c08a23 Fix failing tests 2026-01-21 16:05:43 +01:00
Matej Bačo 6ebc36d0f3 Fix merge conflict 2026-01-21 14:47:18 +01:00
Matej Bačo e5ed7c835a Merge branch '1.8.x' into chore-php-types 2026-01-21 14:43:22 +01:00
Damodar LohaniandGitHub cbb5963b29 Merge pull request #11172 from appwrite/fix-missing-doc
Fix: missing doc
2026-01-21 19:07:42 +05:45
Damodar Lohani f376db7b26 docs: add get-queue-audits reference documentation 2026-01-21 13:14:28 +00:00
Chirag Aggarwal bfd34b3b15 fix validation 2026-01-21 14:01:16 +05:30
Chirag Aggarwal a66551863a update composer 2026-01-21 13:46:46 +05:30
Chirag Aggarwal 33abd7f0ed Merge branch '1.8.x' into feat-sdk-for-md 2026-01-21 13:46:33 +05:30
Chirag AggarwalandGitHub 633e290bb4 Merge pull request #11171 from appwrite/fix-compression-enabled-env 2026-01-21 11:00:59 +05:30
Chirag Aggarwal 9b14a8f08d Add _APP_COMPRESSION_ENABLED env variable to .env and docker-compose
The _APP_COMPRESSION_ENABLED environment variable is used in http.php
but was missing from .env and docker-compose.yml, preventing it from
being configured in containerized deployments.
2026-01-21 10:41:29 +05:30
Hemachandar d38bf2f909 append variables 2026-01-20 18:07:18 +05:30
Hemachandar b9d5371403 Add addVariable method to Mail event 2026-01-20 17:30:28 +05:30
Chirag AggarwalandGitHub ddbddafbf9 Merge pull request #11161 from appwrite/release-cli-13.0.1 2026-01-19 18:00:59 +05:30
Chirag Aggarwal 86a4bfa74e chore: release cli 13.0.1 2026-01-19 17:58:43 +05:30
Jake BarnbyandGitHub 8124b07860 Merge pull request #11033 from appwrite/add-webhooks-and-functions-events 2026-01-19 22:02:49 +13:00
Jake BarnbyandGitHub a7898b3d5b Merge pull request #11159 from appwrite/feat-graphql-introspection 2026-01-19 21:58:56 +13:00
Jake Barnby 5d24b51421 Allow separately enabling graphql introspection 2026-01-19 19:26:17 +13:00
ArnabChatterjee20k d095f25a94 updated composer 2026-01-19 11:04:24 +05:30
ArnabChatterjee20k 069e4c6001 Merge remote-tracking branch 'origin/1.8.x' into new-pool-adapter 2026-01-19 11:03:32 +05:30
shimon 0203323b4a Remove 'authorization' injection from Bulk Delete, Update, and Upsert classes 2026-01-18 14:01:35 +02:00
shimon 94e29cff53 Fix typo in Authorization parameter in API action definition 2026-01-18 13:11:05 +02:00
shimon 72def3b2fb Refactor API action parameters to include Authorization dependency 2026-01-18 13:05:43 +02:00
shimon d015a75e81 linter 2026-01-18 12:48:36 +02:00
shimon 1306c85eb5 merge with 1.8x 2026-01-18 11:03:06 +02:00
shimon c3ea66b37a Merge branch '1.8.x' of github.com:appwrite/appwrite into add-webhooks-and-functions-events
# Conflicts:
#	app/controllers/shared/api.php
#	app/init/resources.php
#	src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php
#	src/Appwrite/Platform/Modules/Databases/Http/Databases/Transactions/Update.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Delete.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Update.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Bulk/Upsert.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Create.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Transactions/Update.php
2026-01-18 11:01:40 +02:00
Damodar LohaniandGitHub 72ce068714 Merge pull request #11057 from appwrite/feat-health-module
Feat: Health module
2026-01-18 07:27:19 +05:45
Chirag AggarwalandGitHub ea76213dd2 Merge pull request #11146 from appwrite/feat-cleanup-stale-executions 2026-01-17 12:44:15 +05:30
Matej BačoandGitHub 12b79363d1 Merge pull request #11148 from appwrite/feat-new-schema-dualwriting
Feat: Support dual-writing for new schema features
2026-01-16 15:38:23 +01:00
Jake BarnbyandGitHub 0723101397 Merge pull request #11005 from appwrite/dat-969 2026-01-17 03:31:50 +13:00
Matej BačoandGitHub d56a3c1534 Apply suggestion from @Meldiron 2026-01-16 14:53:05 +01:00
DarshanandGitHub b86d7ac3fa Merge pull request #11149 from appwrite/clean-on-async 2026-01-16 19:07:17 +05:30
ArnabChatterjee20k b1fab79dc4 updated query logic in array to be of and format 2026-01-16 19:06:55 +05:30
Darshan 2f066a6ba8 add: check. 2026-01-16 18:41:22 +05:30
Darshan e8ca0610ee fix: type 2026-01-16 18:40:37 +05:30
Darshan 15caa27977 lint. 2026-01-16 18:38:40 +05:30
Darshan 5f22022527 fix: async being missed. 2026-01-16 18:33:27 +05:30
ArnabChatterjee20k b9c7c172ad updated query conversion for nested query 2026-01-16 18:18:24 +05:30
Matej Bačo c2bf1e8040 Merge branch '1.8.x' into feat-new-schema-dualwriting 2026-01-16 13:24:02 +01:00
Matej Bačo cda03f63ab Support dual-writing for new schema features 2026-01-16 13:23:46 +01:00
ArnabChatterjee20k fc04e17a69 updated env 2026-01-16 17:37:56 +05:30
Chirag Aggarwal f5a61fb4d6 feat: add cleanup for stale function executions
Adds a new interval task that marks executions stuck in 'processing'
status for more than 30 minutes as 'failed' with a timeout error.
2026-01-16 17:21:05 +05:30
DarshanandGitHub 429f73eb85 Merge pull request #11147 from appwrite/executions-cleanup 2026-01-16 16:33:39 +05:30
ArnabChatterjee20k da871635d9 Fix namespace import for RuntimeQuery class and update test file accordingly 2026-01-16 16:16:03 +05:30
Darshan ccaea5d010 add: constant. 2026-01-16 16:11:38 +05:30
Darshan b5e9c1786a fix: maintenance logic. 2026-01-16 16:04:54 +05:30
Darshan 79e150b7b2 fix: type. 2026-01-16 16:00:59 +05:30
Darshan beee5e721e upate: run on maintenance as well. 2026-01-16 15:55:45 +05:30
Darshan 0ef4bf21cc address comments. 2026-01-16 15:48:21 +05:30
ArnabChatterjee20k b2486fcb6c Merge remote-tracking branch 'upstream/1.8.x' into dat-969 2026-01-16 15:47:25 +05:30
ArnabChatterjee20k a7b729b9a7 updated deps 2026-01-16 15:33:18 +05:30
Darshan c67b77bca0 update: implement proper logs cleanup! 2026-01-16 15:06:35 +05:30
ArnabChatterjee20k a19705a46f Merge remote-tracking branch 'upstream/1.8.x' into new-pool-adapter 2026-01-16 12:31:57 +05:30
Matej Bačo 5d5a14bd77 PR review fixes 2026-01-15 16:16:09 +01:00
Matej Bačo 1308706023 Merge branch '1.8.x' into chore-php-types 2026-01-15 15:52:51 +01:00
Damodar LohaniandGitHub ce91b1a03d Merge pull request #11143 from appwrite/fix-phone-auth-limit
Fix: auth phone limit
2026-01-15 19:43:54 +05:45
Damodar Lohani fbf390c710 Merge remote-tracking branch 'origin/1.8.x' into feat-health-module 2026-01-15 13:55:09 +00:00
Damodar Lohani 991f5ff9fd Catch exception 2026-01-15 13:19:34 +00:00
Damodar Lohani e8d8373922 Fix: phone auth limit 2026-01-15 12:59:30 +00:00
DarshanandGitHub 0fee1f0ffd Merge pull request #11142 from appwrite/bump-libs 2026-01-15 14:45:28 +05:30
Darshan eb07e99225 bump: sdk-gen. 2026-01-15 13:48:50 +05:30
DarshanandGitHub 325968c52a Merge pull request #11141 from appwrite/fix-specs-descriptions 2026-01-15 12:08:35 +05:30
Jake BarnbyandGitHub 39e66711c8 Merge pull request #11140 from appwrite/fix-db-init 2026-01-15 19:36:45 +13:00
DarshanandGitHub 42a1c8da38 Merge branch '1.8.x' into fix-specs-descriptions 2026-01-15 12:05:19 +05:30
Darshan 206bd63620 regen: specs. 2026-01-15 12:04:46 +05:30
Darshan 75696a21bf update: remove excluded keys from descriptions. 2026-01-15 11:56:19 +05:30
Chirag AggarwalandGitHub 48866acdb7 Merge pull request #11112 from appwrite/release-cli-13.0.0.rc3 2026-01-15 11:53:00 +05:30
Jake BarnbyandClaude Opus 4.5 b1171c661e Add setDatabase() to all project database instances
This completes the fix for utopia-php/database 4.5.2 which removed
the automatic USE database statement. All Database instances that
create or query project databases now have explicit setDatabase()
calls.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 19:08:25 +13:00
Jake Barnby a0e4e89621 Update lock 2026-01-15 18:45:18 +13:00
Jake BarnbyandClaude Opus 4.5 728ed57df0 Fix database initialization after utopia-php/database 4.5.2 update
The 4.5.2 update removed the automatic USE database statement on init,
requiring explicit setDatabase() calls on all database resources.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 18:43:28 +13:00
Chirag AggarwalandGitHub 629479d275 Merge branch '1.8.x' into release-cli-13.0.0.rc3 2026-01-15 10:59:11 +05:30
Jake BarnbyandGitHub 5c922f89fe Merge pull request #11139 from appwrite/feat-pool-resilience 2026-01-15 17:30:13 +13:00
Jake BarnbyandGitHub 6e292349b9 Merge pull request #11138 from appwrite/fix-general-unknown 2026-01-15 16:57:50 +13:00
Jake Barnby 7b940e3a17 Increase + parameterise ppolmax reconnect + sleep 2026-01-15 16:47:19 +13:00
Jake Barnby 7ab3debb10 Format 2026-01-15 16:37:09 +13:00
Jake Barnby c083e1ce74 Throw AppwriteException so handler can unwrap 2026-01-15 16:31:37 +13:00
Jake BarnbyandGitHub 61e886eed4 Merge pull request #11137 from appwrite/feat-auth-instance 2026-01-15 12:28:02 +13:00
Chirag Aggarwal 71ac9c7264 use stable 2026-01-14 21:37:00 +05:30
Chirag Aggarwal f78d5523ce mMerge branch '1.8.x' into release-cli-13.0.0.rc3 2026-01-14 21:34:40 +05:30
Chirag Aggarwal 955e1bcbe9 use stable 2026-01-14 21:29:58 +05:30
Jake Barnby 09a337aa1b Fix validators 2026-01-15 04:10:57 +13:00
Jake Barnby 2cfb5ecfd9 Reapply "Merge pull request #11130 from appwrite/feat-auth-instance"
This reverts commit 38687bc24e.
2026-01-15 04:08:00 +13:00
Jake Barnby 38687bc24e Revert "Merge pull request #11130 from appwrite/feat-auth-instance"
This reverts commit c12cad80bb, reversing
changes made to 2a17429226.

# Conflicts:
#	composer.lock
2026-01-15 03:48:42 +13:00
Jake BarnbyandGitHub 77b2a57968 Merge pull request #11136 from appwrite/fix-versions 2026-01-15 03:16:37 +13:00
Jake Barnby 223bb7f08a Update lock 2026-01-15 03:07:26 +13:00
Matej BačoandGitHub b73abf69d0 Merge pull request #11135 from appwrite/feat-new-file-params
Feat: Add more file params
2026-01-14 14:17:37 +01:00
Matej BačoandGitHub e76ed98590 Merge branch '1.8.x' into feat-new-file-params 2026-01-14 14:17:27 +01:00
Matej Bačo 7b10fe1371 typo fix 2026-01-14 14:17:12 +01:00
Matej Bačo e4abc0e0da Add more file params (encryption, compression) 2026-01-14 13:16:50 +01:00
Chirag AggarwalandGitHub 6478499f1c Merge pull request #11134 from appwrite/fix-execution-status-update 2026-01-14 17:45:30 +05:30
DarshanandGitHub 3bbfbdb063 Merge pull request #11132 from appwrite/fix-sdk-handling 2026-01-14 17:18:59 +05:30
Darshan bc6ecbd22c address comments. 2026-01-14 17:15:33 +05:30
Darshan 4d2f631393 update: exclude the mocks.
update: nice group based blacklisting.
2026-01-14 17:07:33 +05:30
Darshan 479a583ff5 exclude the mocks. 2026-01-14 16:55:47 +05:30
Darshan 08cd610823 exclude the mocks. 2026-01-14 16:47:21 +05:30
Darshan 6cd19bf330 address comment for key values on enums. 2026-01-14 16:42:30 +05:30
Darshan d6d8729983 bump: sdk generator. 2026-01-14 16:36:44 +05:30
Chirag Aggarwal 6c866be9f3 Fix execution status not updating if usage stats trigger fails
Move the execution document update inside the finally block and wrap
it in try-catch to ensure the execution record is always updated,
even if queueForStatsUsage->trigger() throws an exception.
2026-01-14 16:12:51 +05:30
Darshan 0ec515779e update: address comments.
regen: specs.
2026-01-14 15:29:30 +05:30
Darshan 91240e0ca7 update: address comments.
regen: specs.
2026-01-14 15:10:42 +05:30
Darshan e3ae7daab4 regen: specs. 2026-01-14 14:58:42 +05:30
Darshan f482e6de68 update: specs generation to exclude mock auth providers. 2026-01-14 14:58:22 +05:30
Jake BarnbyandGitHub c12cad80bb Merge pull request #11130 from appwrite/feat-auth-instance 2026-01-14 20:21:59 +13:00
Chirag AggarwalandGitHub 2a17429226 Merge pull request #11123 from appwrite/feat-integer-format-int64 2026-01-14 12:15:20 +05:30
Jake Barnby e2627d784b Fix router param 2026-01-14 19:27:51 +13:00
Jake Barnby 5c915ef92f Reapply "Merge pull request #11099 from appwrite/feat-auth-instance"
This reverts commit 321fc8ee70.
2026-01-14 19:07:49 +13:00
Chirag AggarwalandGitHub 5697d983d0 Merge branch '1.8.x' into feat-integer-format-int64 2026-01-14 09:54:19 +05:30
Damodar LohaniandGitHub a5fb10c61d Merge pull request #11129 from appwrite/feat-deployment-success
After deployment success hook
2026-01-14 07:15:50 +05:45
Damodar Lohani 5469b78367 feat: add afterDeploymentSuccess hook to handle post-deployment actions 2026-01-14 00:59:22 +00:00
Damodar Lohani e887859cc0 feat: implement afterDeploymentSuccess hook in builds worker and invoke it post-deployment 2026-01-14 00:57:40 +00:00
Chirag Aggarwal 942cfc4591 Merge branch '1.8.x' into feat-integer-format-int64 2026-01-13 21:26:57 +05:30
Jake Barnby 321fc8ee70 Revert "Merge pull request #11099 from appwrite/feat-auth-instance"
This reverts commit a4734a5de7, reversing
changes made to 15922fb88c.

# Conflicts:
#	composer.lock
2026-01-14 02:37:17 +13:00
DarshanandGitHub 568ac54d3d Merge pull request #11122 from appwrite/bump-flutter-dart 2026-01-13 18:09:08 +05:30
Darshan 432e86ec07 regen specs. 2026-01-13 18:04:14 +05:30
Chirag AggarwalandGitHub fef1b5b5e5 Merge pull request #11125 from appwrite/update-domains-lib 2026-01-13 17:35:29 +05:30
Chirag Aggarwal bfac0f92af chore: update domains lib 2026-01-13 17:11:29 +05:30
Chirag Aggarwal ae6df78020 Add int64 format support for integer attributes
This change adds int64 format specification to integer attribute min/max values in the API response models and updates all OpenAPI/Swagger specifications accordingly. This ensures proper type handling for large integer values that exceed int32 range in client SDKs.

Changes:
- Add 'format: int64' to min/max fields in AttributeInteger and ColumnInteger models
- Regenerate OpenAPI 3.x and Swagger 2.x specs for all platforms (client, console, server)
- Update composer dependencies
2026-01-13 17:00:20 +05:30
Darshan 23d84cf29f update: dart sdk. 2026-01-13 14:21:09 +05:30
Darshan c79019f960 regen: specs. 2026-01-13 13:58:57 +05:30
Chirag AggarwalandGitHub dd78b6dbce Merge branch '1.8.x' into release-cli-13.0.0.rc3 2026-01-13 13:39:27 +05:30
Chirag Aggarwal 81e64afe7a changelog 2026-01-13 13:38:44 +05:30
Chirag Aggarwal 5ef675bcdd release cli sdk 13.0.0 rc5 2026-01-13 13:38:32 +05:30
Jake BarnbyandGitHub a4734a5de7 Merge pull request #11099 from appwrite/feat-auth-instance 2026-01-13 20:35:22 +13:00
Jake Barnby 03812cf7bf Lint 2026-01-13 18:59:55 +13:00
Jake Barnby 515a34c180 Set health adapter auth 2026-01-13 18:52:16 +13:00
Jake Barnby 8e688bad4d Revert ping 2026-01-13 18:36:38 +13:00
Damodar Lohani 4905f7cc48 Merge remote-tracking branch 'origin/1.8.x' into feat-health-module 2026-01-13 05:19:00 +00:00
Jake Barnby 3d27b76051 Fix avatars 2026-01-13 18:05:46 +13:00
Jake Barnby 2d6348bf5d Fix health 2026-01-13 17:22:05 +13:00
Jake Barnby 2131a0cea1 Sync auth 2026-01-13 16:21:43 +13:00
Jake Barnby 9379e4d6c8 Merge remote-tracking branch 'origin/1.8.x' into feat-auth-instance
# Conflicts:
#	app/controllers/api/avatars.php
#	composer.lock
#	src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
2026-01-13 15:46:23 +13:00
Jake BarnbyandGitHub 15922fb88c Merge pull request #11103 from appwrite/avatars-module 2026-01-12 21:28:56 +13:00
Jake BarnbyandGitHub ba9526cce0 Merge pull request #11118 from appwrite/bump-http 2026-01-12 21:16:15 +13:00
Darshan 6dbed494e3 bump: swoole and framework. 2026-01-12 13:29:50 +05:30
Darshan 4eb1e7ae45 Merge branch '1.8.x' into avatars-module 2026-01-12 12:04:56 +05:30
Darshan bdd53212a0 Merge branch '1.8.x' into avatars-module 2026-01-12 12:04:41 +05:30
Darshan 1a69d9d236 remove: avatars controller. 2026-01-12 12:03:58 +05:30
Jake BarnbyandGitHub a860c33969 Merge pull request #11115 from appwrite/delete-subscribers 2026-01-12 18:50:59 +13:00
shimon ffd73dc70f Merge remote-tracking branch 'origin/add-webhooks-and-functions-events' into add-webhooks-and-functions-events 2026-01-11 14:12:22 +02:00
shimon 362a7bef0e Merge branch '1.8.x' of github.com:appwrite/appwrite into add-webhooks-and-functions-events 2026-01-11 14:11:51 +02:00
fogelito d08a6f572c message 2026-01-11 08:30:52 +02:00
fogelito 893c1aa669 formatting 2026-01-11 08:21:30 +02:00
fogelito c34873a8b0 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into delete-subscribers 2026-01-11 08:17:40 +02:00
fogelito 17f60b611b catch 2026-01-11 08:13:39 +02:00
Matej Bačo cd0d609229 quality improv 2026-01-10 17:01:31 +01:00
Matej Bačo 497e5f8d00 tests fixes 2026-01-10 16:57:23 +01:00
Matej Bačo 60e1efb8cb Merge conflict fix 2026-01-10 16:42:45 +01:00
Matej Bačo 3d4b29e0f1 Merge branch '1.8.x' into chore-php-types 2026-01-10 16:42:38 +01:00
Matej Bačo dad21a912e PR review changes 2026-01-10 16:35:09 +01:00
Matej BačoandGitHub 445d7495a3 Merge pull request #11110 from appwrite/feat-async-screenshots
Feat: Async screenshots
2026-01-09 14:35:20 +01:00
Matej Bačo aebbe91c34 formatting fix 2026-01-09 11:25:47 +01:00
Matej Bačo 2a9f9f6851 Fix race condition with screenshot worker updating dpeloyment 2026-01-09 11:25:39 +01:00
Matej Bačo eec023aab3 Simplify screenshot failures 2026-01-09 11:25:25 +01:00
Matej Bačo 71f389e1c0 AI PR review 2026-01-09 10:41:58 +01:00
Matej Bačo d71b289025 Implement screenshot worker 2026-01-08 16:51:04 +01:00
Damodar Lohani f4da9b54e7 improve get certificate 2026-01-08 06:34:11 +00:00
Damodar LohaniandGitHub df84b94df0 Merge branch '1.8.x' into feat-health-module 2026-01-08 12:08:57 +05:45
Damodar Lohani 42bf515c8a Fix typos 2026-01-08 06:21:57 +00:00
Jake BarnbyandGitHub a7d39b5ef7 Merge pull request #11105 from appwrite/bump-migrations 2026-01-08 19:15:29 +13:00
Jake Barnby 4c1417944d Fix bucket stats auth 2026-01-08 18:54:52 +13:00
Damodar LohaniandGitHub 27aee1fb88 Merge pull request #11054 from appwrite/feat-file-create-after-success-hook
Feat: add after success hook for file creation endpoint
2026-01-08 11:31:14 +05:45
Darshan 4319c16584 bump: migrations. 2026-01-08 11:13:16 +05:30
Damodar LohaniandGitHub d68fc9a22e Merge branch '1.8.x' into feat-health-module 2026-01-08 11:26:56 +05:45
Jake Barnby e3d6fc123f Update lock 2026-01-08 18:11:37 +13:00
Jake Barnby e6505b9cf9 Merge remote-tracking branch 'origin/1.8.x' into feat-auth-instance
# Conflicts:
#	composer.lock
2026-01-08 17:48:37 +13:00
Jake Barnby c1ab9da740 Update migrations 2026-01-08 17:48:20 +13:00
Jake Barnby b567cd5341 Update storage module for auth instance 2026-01-08 15:01:30 +13:00
Luke B. SilverandGitHub f2ac01bccf Merge pull request #11104 from appwrite/feat-graceful-workers
feat: graceful workers
2026-01-07 23:23:44 +00:00
loks0n eecfba2a72 feat: graceful workers 2026-01-07 16:50:53 +00:00
shimon 2cfaa2223e feat: inject EventProcessor into Update transaction for enhanced event handling 2026-01-07 18:19:20 +02:00
shimon 1a87bde88e removing blank lines 2026-01-07 17:25:12 +02:00
shimon 4b92e3781f removing blank lines 2026-01-07 17:24:53 +02:00
shimon 65bb8946f6 Merge branch '1.8.x' of github.com:appwrite/appwrite into add-webhooks-and-functions-events 2026-01-07 16:58:23 +02:00
shimon db4dcd164e refactor: integrate EventProcessor for handling function and webhook events; streamline event triggering in database actions 2026-01-07 16:57:57 +02:00
Darshan 384c47f436 Merge remote-tracking branch 'origin/avatars-module' into avatars-module 2026-01-07 19:42:56 +05:30
Darshan dcc926402d fix: path error. 2026-01-07 19:42:50 +05:30
Darshan 6969e446d5 Merge branch '1.8.x' into avatars-module 2026-01-07 19:41:49 +05:30
DarshanandGitHub c8bb939357 Merge branch '1.8.x' into avatars-module 2026-01-07 19:37:28 +05:30
Darshan 02488c853d fix: paths error. 2026-01-07 19:28:44 +05:30
Darshan 92f26c1b5a update: move avatars controller to module structure. 2026-01-07 19:09:56 +05:30
DarshanandGitHub ded06d7435 Merge pull request #11102 from appwrite/add-xlist-totalsize 2026-01-07 18:53:53 +05:30
Matej BačoandGitHub 6c59cef8f4 Merge pull request #11090 from appwrite/fix-project-search
Fix: Allows query search on project
2026-01-07 14:09:56 +01:00
Darshan 46072bb95e fix: test. 2026-01-07 18:32:03 +05:30
Darshan 904b7312d6 address comments. 2026-01-07 18:22:36 +05:30
DarshanandGitHub 68fc6b0600 Merge branch '1.8.x' into add-xlist-totalsize 2026-01-07 18:08:59 +05:30
Darshan 47ea8699d1 add: tests. 2026-01-07 18:05:33 +05:30
Darshan 9ba3f6dfe8 add: totalsize on xlist. 2026-01-07 18:00:04 +05:30
HemachandarandGitHub 3483daef0d Stop publishing rule verification errors to Sentry (#11101) 2026-01-07 17:33:00 +05:30
DarshanandGitHub 9943305640 Merge pull request #11100 from appwrite/fix-dat-1019 2026-01-07 16:40:58 +05:30
DarshanandGitHub 8bb6f01e22 Merge pull request #11069 from appwrite/storage-size 2026-01-07 16:36:06 +05:30
Darshan 2cc7bbc0a4 update: address comment, inline method. 2026-01-07 16:00:52 +05:30
Jake Barnby e9366ac2e6 Merge remote-tracking branch 'origin/1.8.x' into feat-auth-instance
# Conflicts:
#	composer.json
#	composer.lock
#	src/Appwrite/Platform/Workers/Migrations.php
2026-01-07 23:26:41 +13:00
Jake Barnby 22f8a3eab9 Sync merge 2026-01-07 23:24:49 +13:00
Darshan 896e5a517a lint. 2026-01-07 15:42:51 +05:30
Darshan 281dcfc64a add queries to logging. 2026-01-07 15:37:56 +05:30
DarshanandGitHub 457cd53475 Merge branch '1.8.x' into storage-size 2026-01-07 14:00:36 +05:30
Darshan 93e7dc772d Merge remote-tracking branch 'origin/storage-size' into storage-size 2026-01-07 13:57:59 +05:30
Darshan 228d095ee5 address comment and fix tests. 2026-01-07 13:57:41 +05:30
Jake BarnbyandGitHub 43189f8940 Merge pull request #11086 from appwrite/migrations-cleanup 2026-01-07 21:15:38 +13:00
DarshanandGitHub b615e4b901 Merge branch '1.8.x' into storage-size 2026-01-07 13:43:48 +05:30
Darshan ebd6457361 address comment. 2026-01-07 13:39:51 +05:30
fogelito 0d3bcc9b3a message todo 2026-01-07 09:52:23 +02:00
fogelito 61e98a501a composer migration 1.3.* 2026-01-07 09:43:13 +02:00
fogelito 502012ddf7 composer migration 1.3.* 2026-01-07 09:42:21 +02:00
fogelito a6eb479c93 composer migration 2026-01-07 09:38:58 +02:00
fogelito e0d7b418dc Merge branch '1.8.x' of https://github.com/appwrite/appwrite into migrations-cleanup
# Conflicts:
#	composer.lock
2026-01-07 09:37:48 +02:00
Jake Barnby ea97072479 Merge remote-tracking branch 'origin/1.8.x' into feat-auth-instance
# Conflicts:
#	composer.lock
2026-01-07 20:10:26 +13:00
Jake Barnby 7573ee75a2 Use authorization instance 2026-01-07 20:04:28 +13:00
Damodar LohaniandGitHub 0dc00eb2c5 Merge pull request #11095 from appwrite/lohanidamodar-patch-2
Fix deleteAuditLogs function call parameters
2026-01-07 12:41:43 +05:45
Jake BarnbyandGitHub b8ccc3b846 Merge pull request #11096 from appwrite/bump-migrations-lib 2026-01-07 19:18:52 +13:00
Damodar LohaniandGitHub 6f1cac7d76 Merge branch '1.8.x' into feat-health-module 2026-01-07 11:53:34 +05:45
Darshan 5f3384b821 bump. 2026-01-07 11:24:15 +05:30
Darshan 71cb462f49 Merge branch '1.8.x' into 'bump-migrations-lib'. 2026-01-07 11:20:00 +05:30
Darshan 27e859030d use: vcs for now. 2026-01-07 11:15:28 +05:30
Damodar LohaniandGitHub e7a82e4d31 Fix deleteAuditLogs function call parameters 2026-01-07 11:08:26 +05:45
Jake BarnbyandGitHub 6dc836307e Merge pull request #11088 from appwrite/delete-project-skip-validations 2026-01-07 18:10:44 +13:00
shimon 0582cdf394 refactor: clean up whitespace and remove commented-out abuse handling code in api.php 2026-01-06 18:41:06 +02:00
shimon 573d8423a3 refactor: remove unused purgeFunctionEventsCache method and clean up whitespace in Update class 2026-01-06 18:40:17 +02:00
shimon 3fa3318346 Merge branch '1.8.x' of github.com:appwrite/appwrite into add-webhooks-and-functions-events 2026-01-06 18:32:00 +02:00
shimon 23dfb23a3b fix: revert Traefik image version to 2.11; implement caching for function events and webhooks; add cache purging on function create/update/delete events 2026-01-06 18:28:37 +02:00
HemachandarandGitHub fc5ea06821 Bump utopia-php/fetch version (#10997)
* Bump utopia-php/fetch version

* fix timeouts
2026-01-06 21:30:25 +05:30
fogelito 5642983f91 Pull main 2026-01-06 17:26:30 +02:00
fogelito b208e5c066 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into migrations-cleanup
# Conflicts:
#	composer.json
#	composer.lock
2026-01-06 17:26:08 +02:00
Matej BačoandGitHub aa135d5cd4 Merge pull request #11085 from appwrite/feat-success-abuse-reset
Feat: Abuse reset on success
2026-01-06 15:56:36 +01:00
fogelito 6756ee31b6 migration can not be empty 2026-01-06 16:55:36 +02:00
fogelito 89c988d73c finally try catch 2026-01-06 16:36:34 +02:00
Matej Bačo 6d85d1567e Update composer.lock 2026-01-06 15:33:21 +01:00
Matej Bačo 7567639996 grammar fix 2026-01-06 15:18:44 +01:00
Matej Bačo 057bff2140 Merge branch '1.8.x' into feat-success-abuse-reset 2026-01-06 15:18:25 +01:00
Matej Bačo d7bb234072 PR reviews 2026-01-06 15:18:22 +01:00
Matej Bačo c0f8dee4d4 Allows query search on project 2026-01-06 15:03:43 +01:00
fogelito eb10996517 Add try finally 2026-01-06 15:36:08 +02:00
fogelito 31803f0eb9 message 2026-01-06 15:30:19 +02:00
fogelito ac9214f3c4 revert 2026-01-06 14:57:54 +02:00
fogelito 14347d86a8 try disableValidation 2026-01-06 14:49:19 +02:00
fogelito 1b855d2d41 disables validations 2026-01-06 14:37:46 +02:00
HemachandarandGitHub 3b5b15d1a6 Remove dual read for keys (#11083)
* Remove dual read for `keys`

* write to mock

* remove dual writes

* Revert "remove dual writes"

This reverts commit ce9a48423b.

* add todo
2026-01-06 17:49:32 +05:30
Shmuel FogelandGitHub 2c5bc32f7e Merge pull request #11072 from appwrite/platform-rules-collections
Sync platform rules + common targets tables
2026-01-06 13:59:41 +02:00
fogelito 59b41c4b52 lock 2026-01-06 11:16:34 +02:00
fogelito b6e8b55994 Throw 2026-01-06 11:01:38 +02:00
fogelito 3a4fb5dd14 throws 2026-01-06 10:56:33 +02:00
fogelito 2a679ead32 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into migrations-cleanup 2026-01-06 10:45:19 +02:00
fogelito 7da9d480d0 cleanUp 2026-01-06 10:43:16 +02:00
Matej Bačo a37aa2dd68 Generic abuse test group 2026-01-05 22:51:11 +01:00
Matej Bačo b29f70a0af Add new tests 2026-01-05 22:51:03 +01:00
Matej Bačo 2fc5e56a61 WIP: Abuse reset on success 2026-01-05 22:05:00 +01:00
Matej BačoandGitHub b1373c02bc Merge pull request #11056 from appwrite/feat-project-labels
Feat: Project labels
2026-01-05 17:15:30 +01:00
Matej Bačo 1db12e78ef AI code review 2026-01-05 14:42:03 +01:00
Matej Bačo a25c8004e4 Merge branch '1.8.x' into feat-project-labels 2026-01-05 14:32:26 +01:00
Chirag AggarwalandGitHub 547d59d95e Merge pull request #11079 from appwrite/validate-smtp-connection 2026-01-05 17:36:58 +05:30
Chirag Aggarwal 4e0477af32 add timeout to mailer 2026-01-05 17:25:12 +05:30
Luke B. SilverandGitHub 1659bc3ffa Merge pull request #11080 from appwrite/fix-audit-delete
Fix audit delete
2026-01-05 10:53:52 +00:00
fogelito 84e9c8243c fix param order 2026-01-05 09:30:47 +02:00
Chirag Aggarwal 001d38caf5 feat: sdk for md 2026-01-05 12:53:35 +05:30
fogelito 020d7bb801 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into platform-rules-collections 2026-01-04 12:26:49 +02:00
fogelito b6aeaffe8b Remove region index 2026-01-04 12:20:22 +02:00
fogelito 65883122e3 rules changes 2026-01-04 12:16:59 +02:00
shimon cd651dbdb8 chore: update dependencies and fix formatting issues in composer files; change Traefik image version in docker-compose; add debug output in Action.php 2026-01-04 11:35:20 +02:00
shimon 2dfb386b12 Merge branch '1.8.x' of github.com:appwrite/appwrite into add-webhooks-and-functions-events 2026-01-04 10:39:35 +02:00
shimon e9dac6710f Refactor: Remove unused webhook and function event filters, implement caching for function events retrieval 2026-01-04 09:53:29 +02:00
DarshanandGitHub d4660a70c3 Merge pull request #11071 from appwrite/fix-missing-dbId 2026-01-04 12:37:54 +05:30
Darshan 16f9c35850 update: tests. 2026-01-04 12:09:11 +05:30
Darshan 49511ebdd9 fix: missing database id in response. 2026-01-04 12:05:55 +05:30
Luke B. SilverandGitHub 63511c56ae Merge pull request #11067 from appwrite/fix-memory-leak
fix: memory leak
2026-01-03 23:02:31 +00:00
loks0n 445ada0226 fix: memory leak 2026-01-03 22:38:17 +00:00
Darshan a5d4f69c6c bump: specs 2026-01-03 15:43:54 +05:30
Darshan f788fc8f8a add: tests. 2026-01-03 15:42:55 +05:30
Darshan 67ef2ab552 add: return bucket actual size. 2026-01-03 15:13:34 +05:30
Steven NguyenandGitHub 68b4a49e9d Merge pull request #10896 from appwrite/feat-1.8.1-release-prep
Prepare 1.8.1 release
2026-01-02 15:38:50 -08:00
DarshanandGitHub 28a09d7aaa Merge pull request #11065 from appwrite/add-logging-tests 2026-01-02 17:20:10 +05:30
Darshan ee3b2d75ae add: missing test at console level. 2026-01-02 17:01:03 +05:30
Chirag AggarwalandGitHub ffe1fd89a7 Merge pull request #11064 from appwrite/chore-cookie-console 2026-01-02 16:29:39 +05:30
Chirag Aggarwal 06c4ba81e9 chore: sync specs + allow cookie auth in console platform 2026-01-02 15:20:45 +05:30
Chirag AggarwalandGitHub f2772cea6b Merge pull request #11063 from appwrite/fix-remove-production-attribute 2026-01-02 12:22:42 +05:30
Chirag Aggarwal b76a4bdd2f fix: remove production attribute when releasing sdks 2026-01-02 12:16:50 +05:30
Shmuel FogelandGitHub 1c94381830 Merge pull request #11058 from appwrite/cli-previous-errors
Cli previous errors
2026-01-01 17:36:24 +02:00
fogelito 0209d40a38 Cli previous errors 2026-01-01 12:17:20 +02:00
Damodar Lohani 297fae8f81 refactor tests 2026-01-01 09:54:13 +00:00
Damodar Lohani 3e403194e4 Fix tests 2026-01-01 09:37:10 +00:00
Damodar Lohani 0305790e69 Fix: update health status model to use HEALTH_STATUS_LIST 2026-01-01 07:59:06 +00:00
Damodar Lohani 25435aaa11 Error handling 2026-01-01 07:58:18 +00:00
Damodar Lohani 19895e54e3 Fix: health status returning ping in incorrect unit 2026-01-01 07:37:09 +00:00
Damodar Lohani 4b3fe0b6fe Fix missing brace 2026-01-01 07:35:28 +00:00
Damodar LohaniGitHubcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
4ef906b836 Update src/Appwrite/Platform/Modules/Health/Http/Health/Certificate/Get.php
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-01-01 13:15:28 +05:45
Damodar Lohani 28aa4e8a8d refactor and new endpoint test 2026-01-01 05:49:20 +00:00
Damodar Lohani 9c6a6c265a format 2026-01-01 05:45:14 +00:00
Damodar Lohani dc0eb5f7a7 Feat: Health module 2026-01-01 05:45:06 +00:00
Steven NguyenandGitHub 06367eca69 Merge pull request #10954 from appwrite/optional-assistant
Optional assistant
2025-12-31 17:14:37 -08:00
Matej Bačo 8d1acef95d Implement project labels 2025-12-31 15:44:18 +01:00
Matej BačoandGitHub 55343379f2 Merge pull request #11046 from appwrite/chore-module-docs
Chore: Add module docs
2025-12-31 15:19:23 +01:00
DarshanandGitHub a66b565150 Merge branch '1.8.x' into optional-assistant 2025-12-31 19:21:37 +05:30
Darshan ff160871ae update: check assistant status on upgrade as well. 2025-12-31 19:21:19 +05:30
Darshan 92a3eca194 Merge branch '1.8.x' into optional-assistant 2025-12-31 19:09:54 +05:30
Damodar Lohani 03ccca2c35 Add after create success hook in file creation process 2025-12-31 13:05:41 +00:00
Shmuel FogelandGitHub 07135956df Merge pull request #11053 from appwrite/skip-migrations-shutdown
skip migration shutdown
2025-12-31 14:27:41 +02:00
fogelito b5a363a2a7 skip shutdown 2025-12-31 14:01:56 +02:00
Matej BačoandGitHub 31dc9ff873 Merge pull request #11052 from appwrite/chore-upgrade-vcs-version
Chore: Upgrade VCS lib with getCommit fix
2025-12-31 12:22:11 +01:00
Matej Bačo 408f9c2d39 Upgrade VCS with getCommit fix 2025-12-31 11:54:22 +01:00
Matej Bačo 910bd69b16 Ai review fixes 2025-12-31 10:57:10 +01:00
DarshanandGitHub dfd5965d25 Merge pull request #11051 from appwrite/fix-resources 2025-12-31 15:25:55 +05:30
Darshan 2a38eedc98 fix: flaky test!
fix: get resource report what we want.
2025-12-31 15:00:23 +05:30
DarshanandGitHub f875b965be Merge pull request #11049 from appwrite/console-module 2025-12-31 13:20:24 +05:30
Darshan 2e57e5a868 fix: url. 2025-12-31 11:59:01 +05:30
Darshan 69ce99d1b4 feat: console module. 2025-12-31 11:52:41 +05:30
Chirag AggarwalandGitHub 6b881d4aaf Merge pull request #11045 from appwrite/reduce-sdk-release-steps 2025-12-31 10:01:11 +05:30
Damodar LohaniandGitHub aad7d6dbca Merge pull request #10953 from appwrite/feat-audits-upgrade
Feat: Audits  upgrade
2025-12-31 06:55:34 +05:45
Matej Bačo 33ffe4aca7 AI suggestions 2025-12-30 15:13:22 +01:00
Matej Bačo 77eb3d4bcd Update AGENTS.md 2025-12-30 15:12:57 +01:00
Matej Bačo 49989f38f7 Update AGENTS.md 2025-12-30 15:09:47 +01:00
Matej Bačo 18a49ccc44 Update grammar 2025-12-30 15:07:54 +01:00
Matej Bačo 73173a7f8c Add module docs 2025-12-30 15:04:26 +01:00
Chirag Aggarwal e3412bc554 chore: reduce sdk release steps 2025-12-30 18:25:11 +05:30
HemachandarandGitHub c1e50c7abd Write to new resource attributes in keys (#11003)
* Write to new resource attributes in `keys`

* temp for tests

* list keys

* add subqueries

* lint
2025-12-30 17:09:59 +05:30
Luke B. SilverandGitHub 6499f436ca Merge pull request #11040 from appwrite/fix-task-subprocesses
fix: task subprocesses
2025-12-30 09:37:49 +00:00
loks0n 55fbd77bf1 fix: task subprocesses 2025-12-30 09:13:26 +00:00
Luke B. SilverandGitHub 4046829fc6 Merge pull request #11039 from appwrite/chore-remove-proxy-containaer
chore: remove proxy containaer
2025-12-30 08:46:27 +00:00
Chirag AggarwalandGitHub 57e218266f Merge pull request #11043 from appwrite/fix-include-services 2025-12-30 14:09:19 +05:30
Chirag AggarwalandGitHub 188dd5c423 Merge branch '1.8.x' into fix-include-services 2025-12-30 13:51:15 +05:30
Chirag Aggarwal d8d3f22883 fix: remove storage service from include_once 2025-12-30 13:42:39 +05:30
ArnabChatterjee20kandGitHub f338a819e1 Merge branch '1.8.x' into new-pool-adapter 2025-12-30 13:23:01 +05:30
ArnabChatterjee20k 2e82f0c2ec fix: update coroutine pool handling and configuration 2025-12-30 13:03:59 +05:30
Damodar LohaniandGitHub 15dc316535 Merge branch '1.8.x' into feat-audits-upgrade 2025-12-30 07:44:10 +05:45
loks0n 7581019e76 chore: remove proxy container 2025-12-29 18:51:37 +00:00
Matej Bačo 00b5236dea simplify diff 2025-12-29 18:41:35 +01:00
Damodar LohaniandGitHub c4fcec0a8a Merge pull request #11035 from appwrite/feat-storage-module
Feat: storage module
2025-12-29 19:34:15 +05:45
Damodar Lohani ca877fa71d Catch query parse exceptions 2025-12-29 13:24:24 +00:00
Damodar LohaniandGitHub 425dd9639c Merge branch '1.8.x' into feat-storage-module 2025-12-29 18:17:15 +05:45
Matej Bačo df57b59786 Merge branch '1.8.x' into chore-php-types 2025-12-29 13:24:50 +01:00
Matej Bačo eda189dbf1 AI review improvements 2025-12-29 13:24:26 +01:00
Chirag AggarwalandGitHub e60391bdb3 Merge pull request #11037 from appwrite/more-sdk-config 2025-12-29 17:45:11 +05:30
Chirag Aggarwal 57157a71b4 chore: more sdk config flexibility 2025-12-29 16:44:19 +05:30
Shmuel FogelandGitHub 8a52975048 Merge pull request #11036 from appwrite/functions-skip-subqueiry
Functions skip variables subquery
2025-12-29 13:08:00 +02:00
HemachandarandGitHub 45e3267930 Merge pull request #10985 from appwrite/ser-331
Support for sync-ing certificate generation status
2025-12-29 16:20:50 +05:30
Hemachandar 2b96d60c1e copilot - code quality 2025-12-29 16:01:19 +05:30
Matej Bačo ee911e3df6 Finalize unit key tests 2025-12-29 11:21:49 +01:00
fogelito 417bb22790 use Query::contains 2025-12-29 11:55:21 +02:00
Matej Bačo eb2c616089 Improve key unit tests 2025-12-29 10:47:27 +01:00
Hemachandar 1b70bc812b keep certificate renewal in maintenance worker 2025-12-29 14:51:59 +05:30
HemachandarandGitHub e7e429142f Merge branch '1.8.x' into ser-331 2025-12-29 14:20:20 +05:30
Damodar Lohani 80bb9a1f33 fixe endpoints 2025-12-29 08:41:38 +00:00
fogelito 774e3af61c skip variables subquery 2025-12-29 10:20:32 +02:00
Matej Bačo 5f5d9b4fcb Add async key cleanup 2025-12-29 09:04:47 +01:00
Matej Bačo 7c56a76feb self PR review fixes 2025-12-29 08:59:07 +01:00
Damodar Lohani 30373980f1 fix update endpoint 2025-12-29 07:56:46 +00:00
Damodar Lohani 8cf12f685a remove controller 2025-12-29 07:44:08 +00:00
Damodar Lohani f99cb20d05 Initialize storage module and remove storage and fix remaining endpoints 2025-12-29 07:44:03 +00:00
Damodar LohaniandGitHub a7724028b3 Merge branch '1.8.x' into feat-audits-upgrade 2025-12-29 13:23:20 +05:45
ArnabChatterjee20kandGitHub 7026052e04 Merge branch '1.8.x' into new-pool-adapter 2025-12-29 11:58:36 +05:30
Chirag AggarwalandGitHub a0514d2aac Merge pull request #11032 from appwrite/more-sdk-fixes 2025-12-29 11:52:04 +05:30
Chirag AggarwalandGitHub ac137cee3d Merge pull request #11027 from appwrite/lohanidamodar-patch-2 2025-12-29 11:51:00 +05:30
Damodar Lohani f4f4ad9c7d format 2025-12-29 01:51:07 +00:00
Damodar Lohani da7738edaa Feat: Storage module 2025-12-29 01:50:39 +00:00
shimon 6c1f967509 add functionsEvents and webhooksEvents 2025-12-28 18:10:44 +02:00
Chirag Aggarwal 58e091099f fix grammar 2025-12-28 21:17:43 +05:30
Chirag Aggarwal 6410a245eb fix: sdk configuration options 2025-12-28 20:59:46 +05:30
fogelito 670edd845e Merge branch '1.8.x' of https://github.com/appwrite/appwrite into lohanidamodar-patch-2 2025-12-28 17:23:10 +02:00
Chirag AggarwalandGitHub 834c18f9fa Merge pull request #11031 from appwrite/fix-default-namespace-in-sdks 2025-12-28 20:50:35 +05:30
Chirag Aggarwal 77930a0cec fix: default namespace in sdks 2025-12-28 20:46:23 +05:30
fogelito 5d9201466b functions + databases 2025-12-28 16:54:38 +02:00
fogelito d583b5e228 All again 2025-12-28 15:33:00 +02:00
fogelito c463a9a662 Only functions 2025-12-28 15:14:56 +02:00
fogelito bd9f02b689 Only functions 2025-12-28 15:01:29 +02:00
fogelito 472e2c282d skip specific filters 2025-12-28 14:31:46 +02:00
fogelito 7a89779827 revert 2025-12-28 14:17:48 +02:00
fogelito 126de78ce2 skip functions 2025-12-28 14:13:56 +02:00
fogelito e919896eef Merge remote-tracking branch 'origin/lohanidamodar-patch-2' into lohanidamodar-patch-2 2025-12-28 13:49:05 +02:00
fogelito fd3475f945 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into lohanidamodar-patch-2 2025-12-28 13:47:29 +02:00
Damodar Lohani 9c8f565211 use the helper method instead 2025-12-28 10:33:12 +00:00
Damodar Lohani 08f30224b6 Use disable instead of skip agin 2025-12-28 10:26:21 +00:00
Damodar LohaniandGitHub 6d02554f2a Merge pull request #11026 from appwrite/fix-file-permissions
Fix: Document/File set user permission only if not privileged user
2025-12-28 15:43:35 +05:45
Damodar Lohani 61c619f373 remove disables 2025-12-28 09:27:21 +00:00
Damodar LohaniandGitHub 419d56b4b1 Merge branch '1.8.x' into feat-audits-upgrade 2025-12-28 14:58:36 +05:45
Damodar Lohani b32dd316e1 Fix: use skip filters instead 2025-12-28 09:09:11 +00:00
Matej Bačo 04f660e44b Dedicate project test 2025-12-28 10:06:29 +01:00
Damodar Lohani d2cda9770b Use get audit resource for audit cleanup. 2025-12-28 09:01:37 +00:00
Damodar LohaniandGitHub 22042b4749 Merge branch '1.8.x' into fix-file-permissions 2025-12-28 14:23:31 +05:45
Damodar LohaniandGitHub aa17eeb6e3 Disable filters for platform and project databases - Stat resources
- Disable filters for platform and project databases - Stat resources
2025-12-28 14:19:28 +05:45
Shimon NewmanandGitHub 1529357821 Merge pull request #11022 from appwrite/fix-bulk-insert-webhook-validation
add webhook exist validation in bulkTrigger()
2025-12-28 09:31:13 +02:00
Damodar Lohani 3a98361736 add test 2025-12-28 02:02:34 +00:00
Damodar Lohani 0c425dbac3 Fix: assign user permission to files/documents only if not a previleged user 2025-12-28 01:48:23 +00:00
Matej Bačo b4c1b96d43 Fix General tests 2025-12-27 19:28:08 +01:00
Matej Bačo 44d8c66e95 Merge branch '1.8.x' into chore-php-types 2025-12-27 19:18:45 +01:00
Matej Bačo 6774de4eef add todo 2025-12-27 19:18:25 +01:00
Matej Bačo ee0f15eed6 QA bug fixing 2025-12-27 19:08:12 +01:00
Matej Bačo c54d1d29a5 Update stats of all key ypes 2025-12-27 18:44:01 +01:00
Chirag AggarwalandGitHub 69b861cd87 Merge pull request #11025 from appwrite/fix-specs-generation 2025-12-27 21:45:56 +05:30
Chirag Aggarwal 0599d23cc5 fix: specs generation getPlatforms method 2025-12-27 20:48:33 +05:30
Chirag AggarwalandGitHub a37b4a40d9 Merge pull request #11024 from appwrite/turnoff-warning-logs 2025-12-27 20:21:07 +05:30
Chirag Aggarwal 7e37046959 chore: remove warning logs when skipping ssl certificate generation 2025-12-27 20:19:27 +05:30
shimon f9efdfd98e fix: use nullsafe operator for project attribute retrieval in Action class 2025-12-25 19:07:51 +02:00
shimon d53090647b Merge branch '1.8.x' of github.com:appwrite/appwrite into fix-bulk-insert-webhook-validation 2025-12-25 18:56:30 +02:00
shimon e8b8a8fe52 add webhook exist validation in balkTrigger() 2025-12-25 18:52:57 +02:00
ArnabChatterjee20k 3b4196735a refactor: simplify query handling in Realtime adapter and enhance error messaging for unsupported queries 2025-12-24 21:02:03 +05:30
ArnabChatterjee20k 7e315f79cc refactor: improve query handling in Realtime adapter and update RuntimeQuery filter logic 2025-12-24 20:50:05 +05:30
ArnabChatterjee20k 336bd44826 fixed payload in adapter 2025-12-24 20:10:00 +05:30
ArnabChatterjee20k 881d96a653 linting 2025-12-24 19:06:34 +05:30
ArnabChatterjee20k 874e5f61f0 Merge remote-tracking branch 'upstream/1.8.x' into dat-969 2025-12-24 19:01:13 +05:30
ArnabChatterjee20k 39cf207df9 re 2025-12-24 18:57:58 +05:30
ArnabChatterjee20k 11f23d99fb Revert "temp-check: force purging cache"
This reverts commit 00e00ff166.
2025-12-24 17:09:01 +05:30
ArnabChatterjee20k 00e00ff166 temp-check: force purging cache 2025-12-24 16:55:13 +05:30
ArnabChatterjee20kandGitHub 57c80d5967 Merge branch '1.8.x' into new-pool-adapter 2025-12-24 16:48:25 +05:30
ArnabChatterjee20k f36dd2748c fixed tests 2025-12-24 16:22:02 +05:30
Damodar Lohani 69430154e6 update autit to rc version 2025-12-24 10:24:51 +00:00
Damodar LohaniandGitHub 0601305545 Merge branch '1.8.x' into feat-audits-upgrade 2025-12-24 15:21:49 +05:45
Chirag AggarwalandGitHub 73d3d15ed8 Merge pull request #11017 from appwrite/fix-getscreenshot-use-image-format 2025-12-24 14:42:54 +05:30
ArnabChatterjee20k 2baa33351d fix tests 2025-12-24 14:38:43 +05:30
ArnabChatterjee20k f7668e0411 * added concurrent traffic load tests for realtime
* added assert eventual case for project console
* linting
2025-12-24 13:41:31 +05:30
Chirag Aggarwal b78be65376 fix: make getScreenshot output param to use ImageFormat enum 2025-12-24 13:39:51 +05:30
Damodar Lohani 5519086c29 Use db 3.x audit 2025-12-24 01:30:02 +00:00
Damodar Lohani 33b9f33f44 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into feat-audits-upgrade 2025-12-24 01:26:49 +00:00
Luke B. SilverandGitHub 0e75793bef Merge pull request #11013 from appwrite/fix-tests-jwt
Fix JWT test
2025-12-23 17:23:36 +00:00
Matej Bačo 2f4a362e1b Merge branch '1.8.x' into fix-tests-jwt 2025-12-23 16:31:32 +01:00
Matej Bačo cda843d8f6 Fix JWT test 2025-12-23 16:31:17 +01:00
ArnabChatterjee20k 62e881b7e9 reverted the use retry mechanism of the pools 2025-12-23 20:34:59 +05:30
Matej BačoandGitHub d6a40a35ee Merge pull request #11012 from appwrite/chore-increase-jwt-abuse
Increase JWT abuse limit
2025-12-23 15:48:00 +01:00
Matej Bačo e111b4cc18 Increase JWT abuse limit 2025-12-23 15:42:14 +01:00
Matej Bačo c08acedf6a Fix key test 2025-12-23 15:12:41 +01:00
ArnabChatterjee20k c03cd258ce changed the pool size distribution 2025-12-23 18:30:53 +05:30
Matej Bačo 9477a5d980 Fix extensability of collections 2025-12-23 13:30:43 +01:00
ArnabChatterjee20k 7e4d404549 updated composer json 2025-12-23 17:38:52 +05:30
Matej Bačo c0c1d693c2 DB schema update for keys 2025-12-23 13:06:25 +01:00
Matej Bačo 6e47fb6c70 Implement auth for organization and account keys 2025-12-23 13:06:19 +01:00
ArnabChatterjee20k 8df22db3eb added swoole based pools for realtime 2025-12-23 17:33:34 +05:30
Chirag AggarwalandGitHub cdbd80ceda Merge pull request #11010 from appwrite/update-schedule-functions 2025-12-23 17:26:09 +05:30
Hemachandar 58751bbdf1 lint 2025-12-23 16:49:35 +05:30
Matej Bačo cca49f8f6a Improve docs 2025-12-23 12:11:01 +01:00
Matej Bačo b0bd9e5b78 Fix security requirement 2025-12-23 12:09:07 +01:00
HemachandarandGitHub 21334ecdff Merge branch '1.8.x' into ser-331 2025-12-23 16:37:22 +05:30
Matej Bačo 6f7cca7d68 Allow key header for account keys 2025-12-23 12:06:30 +01:00
Hemachandar 47ea2893c3 rename task to Interval 2025-12-23 16:32:43 +05:30
Matej Bačo bce3ce85d5 account key support 2025-12-23 11:59:38 +01:00
Chirag Aggarwal bbeca28026 refactor use env variables for queue and class names 2025-12-23 16:09:42 +05:30
Chirag Aggarwal 9ab9f6d709 chore: update schedule functions task 2025-12-23 15:06:29 +05:30
Chirag AggarwalandGitHub 309fde38f7 Merge pull request #11001 from appwrite/update-sdks-console 2025-12-23 13:48:02 +05:30
Chirag Aggarwal 3310d271bf fix import 2025-12-23 12:35:48 +05:30
Chirag Aggarwal 4e12b6236e Merge branch '1.8.x' into update-sdks-console 2025-12-23 12:34:23 +05:30
Torsten DittmannandGitHub 4df8157d74 Merge pull request #11009 from appwrite/feat-allow-custom-jwt-duration 2025-12-22 21:50:45 +04:00
Torsten Dittmann 37e76e52f6 Make JWT expiry configurable for account JWTs 2025-12-22 21:27:29 +04:00
Torsten Dittmann cd824faf64 Add test for JWT with custom duration 2025-12-22 21:19:50 +04:00
Torsten Dittmann 96b11b02e6 feat: allow custom jwt duration 2025-12-22 21:08:48 +04:00
ArnabChatterjee20k b7e2606b9f Enhance Realtime functionality with query support and improve tests
- Updated Realtime adapter to handle queries during subscription.
- Added query filtering capabilities in RuntimeQuery class.
- Modified RealtimeBase and RealtimeCustomClientTest to support query parameters in WebSocket connections.
- Improved test coverage for account and database channels with queries.
2025-12-22 18:01:00 +05:30
Matej Bačo 09d71e73af keys list to be public as its reused for more types of keys 2025-12-22 11:37:38 +01:00
Matej Bačo 465912822f Mark reused key response public 2025-12-22 11:32:45 +01:00
Chirag Aggarwal 9f24cb4aa3 trigger ci 2025-12-22 12:48:23 +05:30
Chirag Aggarwal 9e4e23ad3f chore: update sdks script console log 2025-12-21 22:03:30 +05:30
Matej Bačo ca43281fa9 Simplify PR 2025-12-20 09:20:39 +01:00
Matej Bačo c69382f29f Fix invalid index 2025-12-19 16:11:43 +01:00
Matej Bačo d45d27b780 Merge branch '1.8.x' into chore-php-types 2025-12-19 16:06:46 +01:00
Jake BarnbyandGitHub 3e96f9c2f1 Merge pull request #10990 from appwrite/project-queries
Allow queries on projects xlist
2025-12-19 14:39:47 +00:00
Jake BarnbyandGitHub dd9f6ed820 Merge pull request #10996 from appwrite/revert-10872-refactor-auth-single-instance
Revert "Refactor auth single instance"
2025-12-19 14:37:41 +00:00
Darshan f7f1bc8352 Merge remote-tracking branch 'origin/project-queries' into project-queries 2025-12-19 19:36:17 +05:30
Darshan bf50fbeb6c bump: order for easier review. 2025-12-19 19:36:05 +05:30
DarshanandGitHub 1f7bfb3352 Merge branch '1.8.x' into project-queries 2025-12-19 19:33:22 +05:30
Darshan 406181e887 revert: to method call 2025-12-19 19:31:05 +05:30
Jake BarnbyandGitHub a7b243aff9 Merge pull request #10992 from appwrite/update-header-name
Update: Header name.
2025-12-19 13:56:00 +00:00
Jake BarnbyandGitHub dc85d44647 Revert "Refactor auth single instance" 2025-12-20 02:54:50 +13:00
Jake BarnbyandGitHub 71621f4ccf Merge pull request #10995 from appwrite/revert-10987-fix-auth
Revert "Fix auth calls"
2025-12-19 13:54:38 +00:00
Jake BarnbyandGitHub 68deee4a56 Revert "Fix auth calls" 2025-12-20 02:54:21 +13:00
Matej BačoandGitHub a20392d13a Merge pull request #10993 from appwrite/feat-agents-md
Feat: Create AGENTS.md
2025-12-19 14:11:14 +01:00
Matej BačoandGitHub 859d146e85 Apply suggestions from code review 2025-12-19 13:41:18 +01:00
Matej Bačo 69d5ce0f55 Switch over to resource-based key DB structure 2025-12-19 13:40:32 +01:00
Darshan e43292052c ci: empty commit 2025-12-19 18:02:07 +05:30
Matej Bačo ee7103c4b0 Typo fix 2025-12-19 13:20:57 +01:00
Matej Bačo 1b1ef80b10 Create AGENTS.md 2025-12-19 13:09:48 +01:00
Matej Bačo 0a3877a900 New DB schema 2025-12-19 13:09:34 +01:00
Darshan 33f90fcf6a re-add headers. 2025-12-19 17:02:55 +05:30
Matej Bačo 31b9ff4f05 Merge branch '1.8.x' into chore-php-types 2025-12-19 12:26:51 +01:00
Matej Bačo 1c3f778da9 PR review fix 2025-12-19 12:26:15 +01:00
Darshan 2f1eee390b update: header name. 2025-12-19 16:23:22 +05:30
Darshan 35bf13cfab update: variable name :D 2025-12-19 16:19:15 +05:30
Darshan f814fd980b update: address comments. 2025-12-19 16:16:56 +05:30
Darshan 3ddfd9c3b0 update: address comment. 2025-12-19 15:52:19 +05:30
DarshanandGitHub 75d8110ad9 Merge branch '1.8.x' into project-queries 2025-12-19 14:23:25 +05:30
Darshan c958f3f1cb update: allow queries on projects xlist. 2025-12-19 14:13:52 +05:30
Steven NguyenandGitHub 06e3a144a3 Merge pull request #10939 from appwrite/fix-smtp-auth-check
Fix smtp auth check
2025-12-18 17:55:43 -08:00
Steven Nguyen 36519c13a8 Merge branch '1.8.x' into fix-smtp-auth-check 2025-12-18 17:21:03 -08:00
Matej Bačo 6f16b56f31 Allow Key extensions 2025-12-18 15:55:11 +01:00
Matej Bačo 59f178d634 Improve PHP types for extensability 2025-12-18 13:37:50 +01:00
Hemachandar cf90653deb lint 2025-12-18 17:30:08 +05:30
Hemachandar 16fb25ce5a tiny 2025-12-18 17:10:02 +05:30
Hemachandar e78887001a spacing 2025-12-18 16:18:23 +05:30
Hemachandar aa4ecdf138 emit events 2025-12-18 16:09:48 +05:30
Hemachandar 3783378288 add action to payload 2025-12-18 15:14:30 +05:30
Hemachandar 5aff78a619 Merge branch '1.8.x' into ser-331 2025-12-18 14:33:34 +05:30
Hemachandar 62b391ef68 certificate status exception 2025-12-18 13:46:56 +05:30
Hemachandar 6f1d2d094a rename task 2025-12-18 13:00:49 +05:30
Hemachandar 88bd35ce98 periodic task for rule verification & certificate generation 2025-12-18 12:40:24 +05:30
Hemachandar db0dbeb27b simplify 2025-12-17 21:04:15 +05:30
Hemachandar 0e27088e2a change function signature 2025-12-17 20:54:18 +05:30
Hemachandar 70a7deaa38 Refactor certificate generation in worker 2025-12-17 20:46:54 +05:30
Damodar Lohani 8baa5cb659 Merge remote-tracking branch 'origin/1.8.x' into feat-audits-upgrade 2025-12-17 08:59:05 +00:00
Chirag AggarwalandGitHub ddb0d5db26 Merge branch '1.8.x' into fix-smtp-auth-check 2025-12-16 10:51:01 +05:30
Damodar LohaniandGitHub cec5cd7bdc Merge branch '1.8.x' into feat-audits-upgrade 2025-12-15 09:44:52 +05:45
Damodar Lohani 63c2d72b2e Fix logs 2025-12-15 03:58:00 +00:00
Damodar Lohani 801219374c Fix queries 2025-12-15 02:50:21 +00:00
Damodar Lohani dc3459edd0 remove dump 2025-12-15 02:43:27 +00:00
Damodar Lohani ace9d86744 fix attribute 2025-12-15 02:41:56 +00:00
Damodar Lohani 2d3e34fd5a fix namespace 2025-12-15 02:20:10 +00:00
Damodar Lohani 69ad4ae930 Fix format 2025-12-15 02:15:45 +00:00
Damodar Lohani 0037305e16 Fix: failing tests 2025-12-15 02:12:35 +00:00
Damodar Lohani 1a205c3f3b upgrade audit 2025-12-14 08:50:39 +00:00
Damodar Lohani 99966b5a24 upgrade audit 2025-12-14 08:35:40 +00:00
Damodar Lohani 2437d2be77 Upgrade audit and fix 2025-12-14 08:30:22 +00:00
Damodar Lohani 2043470bd0 fix typo 2025-12-14 08:00:31 +00:00
Damodar Lohani 667285d536 fix tyupe 2025-12-14 07:58:16 +00:00
Damodar Lohani 1199c1fc52 fix typo 2025-12-14 07:57:37 +00:00
Damodar Lohani f270e47b48 more fixes to audit queries 2025-12-14 07:50:21 +00:00
Darshan 5678860d13 add: optional support for assistant. 2025-12-14 13:07:26 +05:30
Damodar Lohani b83125a41e Fix audits creation 2025-12-14 07:22:39 +00:00
Damodar Lohani 2dcb131778 fix collection name 2025-12-14 02:34:33 +00:00
Damodar Lohani 30083598c6 fix audit 2025-12-14 02:33:17 +00:00
Damodar Lohani a0599d2658 Upgrade audit 2025-12-14 02:31:24 +00:00
Damodar Lohani c50db111d6 format 2025-12-14 02:19:53 +00:00
Damodar Lohani 9441295510 Feat: Audits upgrade 2025-12-14 01:43:35 +00:00
Steven NguyenandGitHub d4517384dc fix: update project tests to reflect changes in project count and names 2025-12-12 07:16:49 +00:00
Steven NguyenandGitHub a94ccdb4f5 fix: enhance SMTP authentication by using env vars 2025-12-12 06:02:17 +00:00
Steven NguyenandGitHub 9518764cae Merge branch '1.8.x' into fix-smtp-auth-check 2025-12-12 05:24:33 +00:00
Steven NguyenandGitHub d174233cd6 fix: update SMTP configuration and enhance validation checks 2025-12-12 04:58:59 +00:00
Steven NguyenandGitHub 3d0aa74ffe Merge pull request #10863 from VijaykumarPujar-tech/new_branch
Fix: Implement mandatory authentication check for Custom SMTP configuration
2025-12-11 13:51:27 -08:00
VijaykumarPujar-tech e0a937912c Added Validation check for username and password 2025-12-09 23:46:44 +05:30
VijaykumarPujar-tech 8951a8465c Added the projects.php changes back 2025-12-09 23:30:34 +05:30
VijaykumarPujar-tech 2fa95b52a0 Reverted some added changes 2025-12-09 22:40:51 +05:30
Vijaykumar PujarandGitHub 0f84f21bb4 Merge branch 'main' into new_branch 2025-12-08 20:31:25 +05:30
VijaykumarPujar-tech a14d5c584e Fix: robust SMTP validation and added regression test 2025-12-08 20:22:18 +05:30
Steven NguyenandGitHub bd288ce650 Merge pull request #10898 from appwrite/copilot/update-changes-md-for-1-8-1
Add CHANGES.md section for version 1.8.1
2025-12-04 17:36:59 -08:00
copilot-swe-agent[bot]andstnguyen90 9f2105b294 Address additional review feedback on CHANGES.md
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
2025-12-05 00:48:50 +00:00
Steven NguyenandGitHub b3944c678c Merge pull request #10903 from appwrite/copilot/add-migration-version-for-1-8-1
Complete V23 migration for 1.8.1: add user email attributes and fix fall-through bug
2025-12-04 16:34:33 -08:00
copilot-swe-agent[bot]andstnguyen90 31c8c09060 Address review feedback on CHANGES.md categorization
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
2025-12-05 00:28:49 +00:00
copilot-swe-agent[bot]andstnguyen90 4283671d49 Add user email attributes migration and fix missing break statement in V23
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
2025-12-04 22:15:50 +00:00
copilot-swe-agent[bot] c3a3717bde Initial plan 2025-12-04 22:02:02 +00:00
copilot-swe-agent[bot]andstnguyen90 5cdb59142f Add CHANGES.md section for version 1.8.1
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
2025-12-03 22:11:54 +00:00
copilot-swe-agent[bot] bf6f784826 Initial plan 2025-12-03 22:03:16 +00:00
Steven NguyenandGitHub 33f7fd259b Merge pull request #10897 from appwrite/chore-bump-console-7.5.7
Bump console to version 7.5.7
2025-12-03 13:53:35 -08:00
Steven Nguyen bc99e04b57 feat: bump console to version 7.5.7 2025-12-03 13:26:06 -08:00
Steven Nguyen 10b8f97e96 chore: bump appwrite version to 1.8.1 2025-12-03 13:22:39 -08:00
396 changed files with 39363 additions and 13938 deletions
+8 -3
View File
@@ -2,6 +2,7 @@ _APP_ENV=development
_APP_EDITION=self-hosted
_APP_LOCALE=en
_APP_WORKER_PER_CORE=6
_APP_COMPRESSION_ENABLED=enabled
_APP_COMPRESSION_MIN_SIZE_BYTES=1024
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
@@ -69,8 +70,8 @@ _APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_SMTP_HOST=maildev
_APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMTP_USERNAME=user
_APP_SMTP_PASSWORD=password
_APP_SMS_PROVIDER=sms://username:password@mock
_APP_SMS_FROM=+123456789
_APP_SMS_PROJECTS_DENY_LIST=
@@ -101,9 +102,12 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
_APP_STATS_RESOURCES_INTERVAL=30
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_INTERVAL_DOMAIN_VERIFICATION=60
_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300
_APP_USAGE_STATS=enabled
_APP_LOGGING_CONFIG=
_APP_LOGGING_CONFIG_REALTIME=
_APP_GRAPHQL_INTROSPECTION=enabled
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=4
@@ -125,4 +129,5 @@ _APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_PROJECT_REGIONS=default
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for
_APP_TRUSTED_HEADERS=x-forwarded-for
_APP_POOL_ADAPTER=stack
+11 -11
View File
@@ -223,7 +223,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group abuseEnabled,screenshots
- name: Failure Logs
if: failure()
@@ -312,7 +312,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group abuseEnabled,screenshots
- name: Failure Logs
if: failure()
@@ -322,8 +322,8 @@ jobs:
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
e2e_abuse_enabled:
name: E2E Service Test (Abuse enabled)
runs-on: ubuntu-latest
needs: setup
steps:
@@ -344,7 +344,7 @@ jobs:
docker compose up -d
sleep 30
- name: Run Projects tests with dev keys in dedicated table mode
- name: Run Projects tests in dedicated table mode
run: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
@@ -354,7 +354,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
- name: Failure Logs
if: failure()
@@ -364,8 +364,8 @@ jobs:
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
e2e_dev_keys_shared_mode:
name: E2E Shared Mode Service Test (Dev Keys)
e2e_abuse_enabled_shared_mode:
name: E2E Shared Mode Service Test (Abuse enabled)
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
@@ -394,7 +394,7 @@ jobs:
docker compose up -d
sleep 30
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
- name: Run Projects tests in ${{ matrix.tables-mode }} table mode
run: |
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
@@ -410,7 +410,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
- name: Failure Logs
if: failure()
@@ -420,7 +420,7 @@ jobs:
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
e2e_screenshots_keys:
e2e_screenshots:
name: E2E Service Test (Site Screenshots)
runs-on: ubuntu-latest
needs: setup
+77
View File
@@ -0,0 +1,77 @@
# AGENTS.md
Appwrite is an end-to-end backend server for web, mobile, native, and backend apps. This guide provides context and instructions for AI coding agents working on the Appwrite codebase.
## Project Overview
Appwrite is a self-hosted Backend-as-a-Service (BaaS) platform that provides developers with a set of APIs and tools to build secure, scalable applications. The project uses a hybrid monolithic-microservice architecture built with PHP, running on Swoole for high performance.
**Key Technologies:**
- **Backend:** PHP 8.3+, Swoole
- **Libraries:** Utopia PHP
- **Database:** MariaDB, Redis
- **Cache:** Redis
- **Queue:** Redis
- **Containers:** Docker
## Development Commands
```bash
# Run Appwrite
docker compose up -d --force-recreate --build
# Run specific test
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
# Format code
composer format
```
## Code Style Guidelines
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) coding standard
- Use PSR-4 autoloading
- Strict type declarations where applicable
- Comprehensive PHPDoc comments
### Naming Conventions
#### `resourceType` Naming Rule
When a collection has a combination of `resourceType`, `resourceId`, and/or `resourceInternalId`, the value of `resourceType` MUST always be **plural** - for example: `functions`, `sites`, `deployments`.
Examples:
```php
'resourceType' => 'functions'
'resourceType' => 'sites'
'resourceType' => 'deployments'
```
## Security Considerations
### Critical Security Practices
- **Never hardcode credentials** - Use environment variables
- **Rate limiting** - Respect abuse prevention mechanisms
## Dependencies
Avoid introducing new dependencies other than utopia-php.
## Adding new endpoints
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
## Pull Request Guidelines
### Before Submitting
- Run `composer format`
- Update documentation if adding features
- Add/update tests for your changes
- Check that Docker build succeeds
`docs/specs/authentication.drawio.svg`
## Known Issues and Gotchas
- **Hot Reload:** Code changes require container restart in some cases
- **Logging:** There is no central place for logs, so when debugging, ensure to check all possibly relevant containers
+98
View File
@@ -1,3 +1,101 @@
# Version 1.8.1
## What's Changed
### Notable changes
* Add branch deployments support in [#10486](https://github.com/appwrite/appwrite/pull/10486)
* Add TanStack Start sites support in [#10681](https://github.com/appwrite/appwrite/pull/10681)
* Add Next.js standalone support in [#10747](https://github.com/appwrite/appwrite/pull/10747)
* Add Resend integration in [#10690](https://github.com/appwrite/appwrite/pull/10690)
* Add option to enable/disable image transformations per-bucket in [#10722](https://github.com/appwrite/appwrite/pull/10722)
* Add operators support in [#10735](https://github.com/appwrite/appwrite/pull/10735) and [#10800](https://github.com/appwrite/appwrite/pull/10800)
* Add function and sites stats in [#10786](https://github.com/appwrite/appwrite/pull/10786)
* Add disable count feature in [#10668](https://github.com/appwrite/appwrite/pull/10668)
* Add ElevenLabs site template in [#10782](https://github.com/appwrite/appwrite/pull/10782)
* Add suggested environment variables in [#10795](https://github.com/appwrite/appwrite/pull/10795)
* Update GeoDB database in [#10890](https://github.com/appwrite/appwrite/pull/10890)
* Update Flutter default build runtime in [#10807](https://github.com/appwrite/appwrite/pull/10807)
* Upgrade runtimes in [#10804](https://github.com/appwrite/appwrite/pull/10804)
### Fixes
* Fix duplicate document error while creating file in [#10891](https://github.com/appwrite/appwrite/pull/10891)
* Fix "Update external deployment (authorize)" throwing 500 error due to invalid query in [#10888](https://github.com/appwrite/appwrite/pull/10888)
* Fix error setting user password in [#10889](https://github.com/appwrite/appwrite/pull/10889)
* Fix error generating email MFA challenges in [#10884](https://github.com/appwrite/appwrite/pull/10884)
* Fix file token expiry in [#10877](https://github.com/appwrite/appwrite/pull/10877)
* Fix TanStack Nitro default in [#10860](https://github.com/appwrite/appwrite/pull/10860)
* Fix TanStack builds in [#10767](https://github.com/appwrite/appwrite/pull/10767)
* Fix nullable validation in [#10819](https://github.com/appwrite/appwrite/pull/10819) and [#10778](https://github.com/appwrite/appwrite/pull/10778)
* Fix WebP library in [#10738](https://github.com/appwrite/appwrite/pull/10738)
* Fix batch writes in [#10812](https://github.com/appwrite/appwrite/pull/10812)
* Fix error handler error in [#10719](https://github.com/appwrite/appwrite/pull/10719)
* Fix Next 16 compatibility in [#10713](https://github.com/appwrite/appwrite/pull/10713)
* Fix stats usage memory leak in [#10683](https://github.com/appwrite/appwrite/pull/10683)
* Fix author URL in template deployments in [#10535](https://github.com/appwrite/appwrite/pull/10535)
* Fix VCS lock deletion in [#10691](https://github.com/appwrite/appwrite/pull/10691)
### Miscellaneous
* Add CSV export functionality in [#10546](https://github.com/appwrite/appwrite/pull/10546), [#10750](https://github.com/appwrite/appwrite/pull/10750), [#10813](https://github.com/appwrite/appwrite/pull/10813), and [#10847](https://github.com/appwrite/appwrite/pull/10847)
* Add JWT disposition in [#10867](https://github.com/appwrite/appwrite/pull/10867)
* Add screenshots endpoint in [#10675](https://github.com/appwrite/appwrite/pull/10675)
* Add screenshot endpoint stats in [#10706](https://github.com/appwrite/appwrite/pull/10706)
* Add users attributes in [#10688](https://github.com/appwrite/appwrite/pull/10688)
* Add max build duration environment variable in [#10674](https://github.com/appwrite/appwrite/pull/10674)
* Add custom realtime logger in [#10871](https://github.com/appwrite/appwrite/pull/10871)
* Add logs in [#10869](https://github.com/appwrite/appwrite/pull/10869)
* Improve MFA docs endpoint order in [#10793](https://github.com/appwrite/appwrite/pull/10793)
* Auth refactor in [#10758](https://github.com/appwrite/appwrite/pull/10758), [#10837](https://github.com/appwrite/appwrite/pull/10837), [#10682](https://github.com/appwrite/appwrite/pull/10682), and [#10667](https://github.com/appwrite/appwrite/pull/10667)
* Bump assistant to 0.8.4 in [#10887](https://github.com/appwrite/appwrite/pull/10887)
* Bump database to 3.1.5 in [#10766](https://github.com/appwrite/appwrite/pull/10766)
* Bump Utopia DNS in [#10761](https://github.com/appwrite/appwrite/pull/10761)
* Update domains to 0.8.3 in [#10658](https://github.com/appwrite/appwrite/pull/10658)
* Update domains to 0.9.1 in [#10678](https://github.com/appwrite/appwrite/pull/10678)
* Update Apple Swift to 13.3.0 in [#10679](https://github.com/appwrite/appwrite/pull/10679)
* Update Apple Swift in [#10663](https://github.com/appwrite/appwrite/pull/10663)
* Update CLI to 10.2.2 in [#10672](https://github.com/appwrite/appwrite/pull/10672)
* Update to CLI 12.0.0 in [#10853](https://github.com/appwrite/appwrite/pull/10853)
* Update docs examples to use Permission class in [#10707](https://github.com/appwrite/appwrite/pull/10707)
* Update SDK examples docs in [#10855](https://github.com/appwrite/appwrite/pull/10855)
* Release Python SDK in [#10762](https://github.com/appwrite/appwrite/pull/10762)
* Release Flutter 20.3.2 in [#10838](https://github.com/appwrite/appwrite/pull/10838)
* Release Flutter/Dart add screenshot examples in [#10811](https://github.com/appwrite/appwrite/pull/10811)
* Release PHP CLI in [#10791](https://github.com/appwrite/appwrite/pull/10791)
* Release SDKs in [#10817](https://github.com/appwrite/appwrite/pull/10817)
* Update SDKs in [#10694](https://github.com/appwrite/appwrite/pull/10694), [#10729](https://github.com/appwrite/appwrite/pull/10729), and [#10744](https://github.com/appwrite/appwrite/pull/10744)
* Update SDK generator in [#10743](https://github.com/appwrite/appwrite/pull/10743)
* Update database in [#10664](https://github.com/appwrite/appwrite/pull/10664)
* Update README file in [#10763](https://github.com/appwrite/appwrite/pull/10763)
* SDK release documentation in [#10745](https://github.com/appwrite/appwrite/pull/10745)
* SDK release runtime config in [#10765](https://github.com/appwrite/appwrite/pull/10765)
* Sync specs in [#10789](https://github.com/appwrite/appwrite/pull/10789)
* Sync 1.8.0 in [#10677](https://github.com/appwrite/appwrite/pull/10677)
* Add workflow for issue triage in [#10718](https://github.com/appwrite/appwrite/pull/10718)
* Add issue auto-labeler in [#10700](https://github.com/appwrite/appwrite/pull/10700)
* Add AI moderator repo in [#10717](https://github.com/appwrite/appwrite/pull/10717)
* Browser bump in [#10850](https://github.com/appwrite/appwrite/pull/10850)
* Template type enum override in [#10848](https://github.com/appwrite/appwrite/pull/10848)
* VCS reference type in [#10852](https://github.com/appwrite/appwrite/pull/10852)
* Index scope description in [#10851](https://github.com/appwrite/appwrite/pull/10851)
* Config for environment in [#10833](https://github.com/appwrite/appwrite/pull/10833)
* Format instance in [#10830](https://github.com/appwrite/appwrite/pull/10830)
* Replace sleep in webhooks service in [#10656](https://github.com/appwrite/appwrite/pull/10656)
* Update email composer in [#10720](https://github.com/appwrite/appwrite/pull/10720)
* Update facts on GitHub sites and functions in [#10593](https://github.com/appwrite/appwrite/pull/10593) and [#10771](https://github.com/appwrite/appwrite/pull/10771)
* Fix wrong user type in [#10875](https://github.com/appwrite/appwrite/pull/10875)
* Fix limit and offset computation in [#10880](https://github.com/appwrite/appwrite/pull/10880)
* Fix enum examples in [#10828](https://github.com/appwrite/appwrite/pull/10828)
* Fix response models multi-methods in [#10815](https://github.com/appwrite/appwrite/pull/10815)
* Fix undefined variable in [#10654](https://github.com/appwrite/appwrite/pull/10654)
* Fix undefined sequence in [#10652](https://github.com/appwrite/appwrite/pull/10652)
* Fix description in [#10702](https://github.com/appwrite/appwrite/pull/10702)
* Fix warning in builds worker in [#10705](https://github.com/appwrite/appwrite/pull/10705)
* Fix sites create deployment docs in [#10566](https://github.com/appwrite/appwrite/pull/10566)
* Fix test dependencies projects in [#10655](https://github.com/appwrite/appwrite/pull/10655)
* Fix list sites test in [#10726](https://github.com/appwrite/appwrite/pull/10726)
# Version 1.8.0
## What's Changed
+28 -50
View File
@@ -222,73 +222,51 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
```bash
.
├── app # Main application
│ ├── assets
│ │ ├── dbip
│ │ ├── fonts
│ │ └── security
│ ├── config # Config files
│ │ ├── avatars
│ │ ├── collections
│ │ ├── locale
│ │ ├── specs
│ │ ├── storage
│ │ └── templates
│ ├── controllers # API & dashboard controllers
│ │ ├── api
│ │ ├── shared
│ │ └── web
│ ├── init # DB schemas
│ └── database
── views # HTML server-side templates
├── general
└── install
│ ├── db # DB schemas
├── sdks # SDKs generated copies (used for generating code examples)
── tasks # Server CLI commands
├── views # HTML server-side templates
└── workers # Background workers
├── bin # Server executables (tasks & workers)
├── dev # Debugger config
├── docker # Docker related resources and configs
├── docs # Docs and tutorials
│ ├── examples
│ ├── lists
│ ├── references
│ ├── sdks
│ ├── services
│ ├── specs
│ └── tutorials
├── public # Public files
│ ├── dist
│ ├── fonts
│ ├── images
│ ├── sdk-console
── sdk-project
│ └── sdk-web
├── src # Supporting libraries (each lib has one role, common libs are released as
│ ├── Appwrite
├── Auth
├── Certificates
├── Deletes
├── Detector
├── Docker
├── Event
├── Extend
├── Functions/Validator
├── GraphQL
├── Hooks
├── Messaging
├── Migration
├── Network
├── OpenSSL
├── Platform
── Promises
│ │ ├── PubSub
│ │ ├── SDK
│ │ ├── Task/Validator
│ │ ├── Template
│ │ ├── Transformation
│ │ ├── URL
│ │ ├── Utopia
│ │ └── Vcs
│ └── Executor
│ ├── scripts
── styles
├── src # Supporting libraries (each lib has one role, common libs are released as individual projects)
│ └── Appwrite
├── Auth
├── Detector
├── Docker
| ├── DSN
├── Event
├── Extend
├── GraphQL
├── Messaging
├── Migration
├── Network
├── OpenSSL
├── Promises
├── Specification
├── Task
├── Template
├── URL
── Utopia
└── tests # End to end & unit tests
├── benchmarks
├── e2e
├── extensions
├── resources
└── unit
```
+3 -1
View File
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.10.6 AS base
FROM appwrite/base:0.11.5 AS base
LABEL maintainer="team@appwrite.io"
@@ -57,6 +57,7 @@ RUN mkdir -p /storage/uploads && \
# Executables
RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/install && \
chmod +x /usr/local/bin/interval && \
chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/realtime && \
@@ -76,6 +77,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/queue-count-success && \
chmod +x /usr/local/bin/worker-audits && \
chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-screenshots && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-databases && \
chmod +x /usr/local/bin/worker-deletes && \
+3 -3
View File
@@ -72,7 +72,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.8.0
appwrite/appwrite:1.8.1
```
### Windows
@@ -84,7 +84,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.8.0
appwrite/appwrite:1.8.1
```
#### PowerShell
@@ -94,7 +94,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.8.0
appwrite/appwrite:1.8.1
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+3 -3
View File
@@ -75,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.8.0
appwrite/appwrite:1.8.1
```
### Windows
@@ -87,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.8.0
appwrite/appwrite:1.8.1
```
#### PowerShell
@@ -97,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.8.0
appwrite/appwrite:1.8.1
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+11
View File
@@ -78,6 +78,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setMetadata('host', \gethostname())
@@ -168,6 +169,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
@@ -189,6 +191,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
@@ -265,6 +268,14 @@ CLI::setResource('logError', function (Registry $register) {
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
}
$log->addExtra('previousFile', $error->getPrevious()->getFile());
$log->addExtra('previousLine', $error->getPrevious()->getLine());
}
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
+2 -2
View File
@@ -26,8 +26,8 @@ unset($common['files']);
$collections = [
'buckets' => $buckets,
'databases' => $databases,
'projects' => array_merge($projects, $common),
'console' => array_merge($platform, $common),
'projects' => array_merge_recursive($projects, $common),
'console' => array_merge_recursive($platform, $common),
'logs' => $logs,
];
+11
View File
@@ -1288,6 +1288,17 @@ return [
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('labels'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
+82 -32
View File
@@ -6,7 +6,7 @@ use Utopia\Database\Helpers\ID;
$providers = Config::getParam('oAuthProviders', []);
return [
$platformCollections = [
'projects' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('projects'),
@@ -330,7 +330,18 @@ return [
'default' => null,
'array' => false,
'filters' => ['datetime'],
]
],
[
'$id' => ID::custom('labels'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
@@ -622,24 +633,35 @@ return [
'name' => 'keys',
'attributes' => [
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
@@ -712,14 +734,14 @@ return [
],
'indexes' => [
[
'$id' => ID::custom('_key_project'),
'$id' => ID::custom('_key_resource'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
'attributes' => ['resourceType', 'resourceInternalId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => '_key_accessedAt',
'$id' => ID::custom('_key_accessedAt'),
'type' => Database::INDEX_KEY,
'attributes' => ['accessedAt'],
'lengths' => [],
@@ -1362,21 +1384,21 @@ return [
'$id' => '_key_type',
'type' => Database::INDEX_KEY,
'attributes' => ['type'],
'lengths' => [32],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_trigger',
'type' => Database::INDEX_KEY,
'attributes' => ['trigger'],
'lengths' => [32],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_deploymentResourceType',
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentResourceType'],
'lengths' => [32],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
@@ -1418,23 +1440,23 @@ return [
'$id' => ID::custom('_key_owner'),
'type' => Database::INDEX_KEY,
'attributes' => ['owner'],
'lengths' => [16],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_region'),
'type' => Database::INDEX_KEY,
'attributes' => ['region'],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_piid_riid_rt'),
'$id' => ID::custom('_key_piid_diid_drt'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'deploymentInternalId', 'deploymentResourceType'],
'lengths' => [],
'orders' => [],
],
[
'$id' => '_key_region_status_createdAt',
'type' => Database::INDEX_KEY,
'attributes' => ['region', 'status', '$createdAt'],
'lengths' => [],
'orders' => [],
],
],
],
@@ -1903,3 +1925,31 @@ return [
'indexes' => []
],
];
// Organization API keys subquery
$platformCollections['teams']['attributes'][] = [
'$id' => ID::custom('keys'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryOrganizationKeys'],
];
// Account API keys subquery
$platformCollections['users']['attributes'][] = [
'$id' => ID::custom('keys'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryAccountKeys'],
];
return $platformCollections;
+106
View File
@@ -567,6 +567,17 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentRetention'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
@@ -765,6 +776,17 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('specification'),
@@ -776,6 +798,28 @@ return [
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('runtimeSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'$id' => ID::custom('scopes'),
'type' => Database::VAR_STRING,
@@ -1035,6 +1079,17 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'$id' => ID::custom('fallbackFile'),
'type' => Database::VAR_STRING,
@@ -1046,6 +1101,17 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentRetention'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
@@ -1200,6 +1266,28 @@ return [
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('runtimeSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'$id' => ID::custom('buildRuntime'),
'type' => Database::VAR_STRING,
@@ -1357,6 +1445,17 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildOutput'),
@@ -2098,6 +2197,13 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_resourceType'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
+25 -1
View File
@@ -357,6 +357,11 @@ return [
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::USER_JWT_AND_COOKIE_SET => [
'name' => Exception::USER_JWT_AND_COOKIE_SET,
'description' => 'JWT and cookie used in the same request. Use either `setJWT` or `setCookie`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::API_KEY_EXPIRED => [
'name' => Exception::API_KEY_EXPIRED,
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
@@ -1074,6 +1079,11 @@ return [
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ACCOUNT_KEY_EXPIRED => [
'name' => Exception::ACCOUNT_KEY_EXPIRED,
'description' => 'The account API key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ROUTER_HOST_NOT_FOUND => [
'name' => Exception::ROUTER_HOST_NOT_FOUND,
'description' => 'Host is not trusted. This could occur because you have not configured a custom domain. Add a custom domain to your project first and try again.',
@@ -1103,7 +1113,6 @@ return [
'name' => Exception::RULE_VERIFICATION_FAILED,
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
'code' => 400,
'publish' => true
],
Exception::PROJECT_SMTP_CONFIG_INVALID => [
'name' => Exception::PROJECT_SMTP_CONFIG_INVALID,
@@ -1329,4 +1338,19 @@ return [
'description' => 'Target has an invalid provider type.',
'code' => 400,
],
Exception::USER_ID_MISSING => [
'name' => Exception::USER_ID_MISSING,
'description' => 'When using account API key, make sure to pass x-appwrite-user header with your user ID.',
'code' => 403,
],
Exception::ORGANIZATION_ID_MISSING => [
'name' => Exception::ORGANIZATION_ID_MISSING,
'description' => 'When using organization API key, make sure to pass x-appwrite-organization header with your organization ID.',
'code' => 403,
],
Exception::PROJECT_ID_MISSING => [
'name' => Exception::PROJECT_ID_MISSING,
'description' => 'When using project API key, make sure to pass x-appwrite-project header with your project ID.',
'code' => 403,
],
];
+21 -38
View File
@@ -50,17 +50,12 @@
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('https://assets.appwrite.io/fonts/poppins/poppins-v23-latin-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
</style>
<style>
body {
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
padding: 32px;
line-height: 1.5;
color: #616b7c;
@@ -68,15 +63,12 @@
font-weight: 400;
font-family: "Inter", sans-serif;
background-color: #ffffff;
margin: 0;
padding: 0;
line-height: 150%;
}
a {
.main a {
color: currentColor;
word-break: break-all;
}
a.button {
.main a.button {
box-sizing: border-box;
display: inline-block;
text-align: center;
@@ -87,8 +79,8 @@
border: 1px solid #414146;
border-radius: 8px;
}
a.button:hover,
a.button:focus {
.main a.button:hover,
.main a.button:focus {
opacity: 0.8;
}
table {
@@ -102,11 +94,6 @@
td {
vertical-align: top;
}
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
}
h1 {
font-size: 22px;
margin-bottom: 0px;
@@ -138,9 +125,6 @@
border: none;
border-top: 1px solid #e8e9f0;
}
h* {
font-family: 'Poppins', sans-serif;
}
p {
margin-bottom: 10px;
}
@@ -155,21 +139,20 @@
<body style="direction: {{direction}}">
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word;
word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
<div class="main" style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word; word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
</body>
</html>
+15
View File
@@ -0,0 +1,15 @@
<?php
// List of scopes for Account API keys (Tokens)
return [
"account" => [
"description" => 'Access to manage account, its organizations, sessions, tokens, and billing.',
],
"teams.read" => [
"description" => 'Access to read account\'s organizations.',
],
"teams.write" => [
"description" => 'Access to create, update and delete account\'s organizations and its memberships.',
],
];
+42
View File
@@ -0,0 +1,42 @@
<?php
// List of scopes for organization (teams) API keys
return [
"platforms.read" => [
"description" => 'Access to read project\'s platforms',
],
"platforms.write" => [
"description" =>
'Access to create, update, and delete project\'s platforms',
],
"projects.read" => [
"description" => 'Access to read organization\'s projects',
],
"projects.write" => [
"description" =>
"Access to create, update, and delete projects in organization",
],
"keys.read" => [
"description" => 'Access to read project\'s API keys',
],
"keys.write" => [
"description" =>
"Access to create, update, and delete project\'s API keys",
],
"devKeys.read" => [
"description" => 'Access to read project\'s development keys',
],
"devKeys.write" => [
"description" =>
"Access to create, update, and delete project\'s development keys",
],
"webhooks.read" => [
"description" =>
"Access to read project\'s webhooks",
],
"webhooks.write" => [
"description" =>
"Access to create, update, and delete project\'s webhooks",
],
];
+25 -3
View File
@@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '20.3.2',
'version' => '20.3.3',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -116,6 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'namespace' => 'io.appwrite',
'version' => '11.4.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
@@ -226,7 +227,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '12.0.1',
'version' => '13.1.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -249,6 +250,26 @@ return [
],
],
],
[
'key' => 'markdown',
'name' => 'Markdown',
'version' => '0.1.0',
'url' => 'https://github.com/appwrite/sdk-for-md.git',
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'markdown',
'source' => \realpath(__DIR__ . '/../sdks/console-md'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-md.git',
'gitRepoName' => 'sdk-for-md',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
],
],
],
@@ -376,7 +397,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '20.1.0',
'version' => '20.1.1',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -395,6 +416,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'namespace' => 'io.appwrite',
'version' => '13.1.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
+6 -6
View File
@@ -20,7 +20,7 @@ return [
'name' => 'Console',
'subtitle' => '',
'description' => '',
'controller' => 'web/console.php',
'controller' => '', // Uses modules
'sdk' => false,
'docs' => false,
'docsUrl' => '',
@@ -48,7 +48,7 @@ return [
'name' => 'Avatars',
'subtitle' => 'The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.',
'description' => '/docs/services/avatars.md',
'controller' => 'api/avatars.php',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/avatars',
@@ -104,7 +104,7 @@ return [
'name' => 'Health',
'subtitle' => 'The Health service allows you to both validate and monitor your Appwrite server\'s health.',
'description' => '/docs/services/health.md',
'controller' => 'api/health.php',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/server/health',
@@ -146,7 +146,7 @@ return [
'name' => 'Storage',
'subtitle' => 'The Storage service allows you to manage your project files.',
'description' => '/docs/services/storage.md',
'controller' => 'api/storage.php',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/storage',
@@ -270,9 +270,9 @@ return [
'console' => [
'key' => 'console',
'name' => 'Console',
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
'subtitle' => 'The Console service allows you to interact with console relevant information.',
'description' => '',
'controller' => 'api/console.php',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => '',
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+68 -40
View File
@@ -20,6 +20,34 @@ function getRuntimes($runtimes, $commands, $entrypoint, $providerRootDirectory,
}));
}
class FunctionUseCases
{
public const STARTER = 'starter';
public const DATABASES = 'databases';
public const AI = 'ai';
public const MESSAGING = 'messaging';
public const UTILITIES = 'utilities';
public const DEV_TOOLS = 'dev-tools';
public const AUTH = 'auth';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::STARTER,
self::DATABASES,
self::AI,
self::MESSAGING,
self::UTILITIES,
self::DEV_TOOLS,
self::AUTH,
];
}
}
return [
[
'icon' => 'icon-lightning-bolt',
@@ -32,7 +60,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['starter'],
'useCases' => [FunctionUseCases::STARTER],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter', $allowList),
...getRuntimes(
@@ -73,7 +101,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -119,7 +147,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -164,7 +192,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -218,7 +246,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -257,7 +285,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -326,7 +354,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -392,7 +420,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['messaging'],
'useCases' => [FunctionUseCases::MESSAGING],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -459,7 +487,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -497,7 +525,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -548,7 +576,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf', $allowList)
],
@@ -571,7 +599,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['dev-tools'],
'useCases' => [FunctionUseCases::DEV_TOOLS],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -616,7 +644,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -669,7 +697,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -754,7 +782,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -853,7 +881,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['messaging'],
'useCases' => [FunctionUseCases::MESSAGING],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -946,7 +974,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['messaging'],
'useCases' => [FunctionUseCases::MESSAGING],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1004,7 +1032,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1091,7 +1119,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1134,7 +1162,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1193,7 +1221,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1229,7 +1257,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1265,7 +1293,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1325,7 +1353,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1385,7 +1413,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1448,7 +1476,7 @@ return [
],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1508,7 +1536,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1545,7 +1573,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1589,7 +1617,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1632,7 +1660,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1669,7 +1697,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1734,7 +1762,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1799,7 +1827,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1856,7 +1884,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1899,7 +1927,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1942,7 +1970,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1986,7 +2014,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2023,7 +2051,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2080,7 +2108,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2153,7 +2181,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['auth'],
'useCases' => [FunctionUseCases::AUTH],
'runtimes' => [
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple', $allowList)
],
+96 -78
View File
@@ -13,7 +13,7 @@ $hostname = $platform['consoleHostname'] ?? '';
$url = $protocol . '://' . $hostname;
class UseCases
class SiteUseCases
{
public const PORTFOLIO = 'portfolio';
public const STARTER = 'starter';
@@ -21,9 +21,27 @@ class UseCases
public const ECOMMERCE = 'ecommerce';
public const DOCUMENTATION = 'documentation';
public const BLOG = 'blog';
public const AI = 'artificial intelligence';
public const AI = 'ai';
public const FORMS = 'forms';
public const DASHBOARD = 'dashboard';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::PORTFOLIO,
self::STARTER,
self::EVENTS,
self::ECOMMERCE,
self::DOCUMENTATION,
self::BLOG,
self::AI,
self::FORMS,
self::DASHBOARD,
];
}
}
const TEMPLATE_FRAMEWORKS = [
@@ -188,7 +206,7 @@ return [
'name' => 'Documentation template',
'tagline' => 'Modern site to store your knowledge with a clean design, full-text search, dark mode, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/template-for-documentation-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-documentation-light.png',
'frameworks' => [
@@ -209,7 +227,7 @@ return [
// When we add Lynx with Appwrite SDK, use following tagline for it:
// 'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-lynx-light.png',
'frameworks' => [
@@ -220,7 +238,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -228,7 +246,7 @@ return [
'name' => 'Vitepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vite.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vitepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vitepress-light.png',
'frameworks' => [
@@ -243,7 +261,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -251,7 +269,7 @@ return [
'name' => 'Vuepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vue.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vuepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vuepress-light.png',
'frameworks' => [
@@ -266,7 +284,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -274,7 +292,7 @@ return [
'name' => 'Docusaurus',
'tagline' => 'Platform for documentation and knowledge sharing powered by React.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/docusaurus-dark.png',
'screenshotLight' => $url . '/images/sites/templates/docusaurus-light.png',
'frameworks' => [
@@ -289,7 +307,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -297,7 +315,7 @@ return [
'name' => 'Nxt Lnk',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/nxt-lnk-dark.png',
'screenshotLight' => $url . '/images/sites/templates/nxt-lnk-light.png',
'frameworks' => [
@@ -308,7 +326,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -316,7 +334,7 @@ return [
'name' => 'Magic Portfolio',
'tagline' => 'Complex personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/magic-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/magic-portfolio-light.png',
'frameworks' => [
@@ -327,7 +345,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -335,7 +353,7 @@ return [
'name' => 'LittleLink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/littlelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/littlelink-light.png',
'frameworks' => [
@@ -346,7 +364,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -354,7 +372,7 @@ return [
'name' => 'Logspot',
'tagline' => 'Website to publish changelogs of your application.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'useCases' => [SiteUseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/logspot-dark.png',
'screenshotLight' => $url . '/images/sites/templates/logspot-light.png',
'frameworks' => [
@@ -368,7 +386,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -376,7 +394,7 @@ return [
'name' => 'Astro Nano',
'tagline' => 'Minimal personal website to showcase your projects, articles, and more.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-nano-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-nano-light.png',
'frameworks' => [
@@ -389,7 +407,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -397,7 +415,7 @@ return [
'name' => 'Astro Starlight',
'tagline' => 'Platform for documentation and knowledge sharing powered by Astro.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/astro-starlight-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlight-light.png',
'frameworks' => [
@@ -410,7 +428,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -418,7 +436,7 @@ return [
'name' => 'Astro Sphere',
'tagline' => 'Modern personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-sphere-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-sphere-light.png',
'frameworks' => [
@@ -431,7 +449,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -439,7 +457,7 @@ return [
'name' => 'Astro Starlog',
'tagline' => 'Platform for publishing written content and media powered by Astro.',
'score' => 5, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'useCases' => [SiteUseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/astro-starlog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlog-light.png',
'frameworks' => [
@@ -452,7 +470,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -460,7 +478,7 @@ return [
'name' => 'Onelink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/onelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/onelink-light.png',
'frameworks' => [
@@ -474,13 +492,13 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
'key' => 'starter-for-flutter',
'name' => 'Flutter starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Flutter application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-flutter-dark.png',
@@ -525,7 +543,7 @@ return [
[
'key' => 'starter-for-js',
'name' => 'JavaScript starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple JavaScript application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-js-dark.png',
@@ -569,7 +587,7 @@ return [
[
'key' => 'starter-for-angular',
'name' => 'Angular starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Angular application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-angular-dark.png',
@@ -615,7 +633,7 @@ return [
[
'key' => 'starter-for-astro',
'name' => 'Astro starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Astro application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-astro-dark.png',
@@ -660,7 +678,7 @@ return [
[
'key' => 'starter-for-analog',
'name' => 'Analog starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Analog application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-analog-dark.png',
@@ -704,7 +722,7 @@ return [
[
'key' => 'starter-for-remix',
'name' => 'Remix starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Remix application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-remix-dark.png',
@@ -748,7 +766,7 @@ return [
[
'key' => 'starter-for-svelte',
'name' => 'Svelte starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Svelte application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png',
@@ -792,7 +810,7 @@ return [
[
'key' => 'starter-for-react',
'name' => 'React starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple React application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png',
@@ -836,7 +854,7 @@ return [
[
'key' => 'starter-for-vue',
'name' => 'Vue starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Vue application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png',
@@ -880,7 +898,7 @@ return [
[
'key' => 'starter-for-react-native',
'name' => 'React Native starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple React Native application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
@@ -924,7 +942,7 @@ return [
[
'key' => 'starter-for-nextjs',
'name' => 'Next.js starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Next.js application integrated with Appwrite SDK.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nextjs-dark.png',
@@ -968,7 +986,7 @@ return [
[
'key' => 'starter-for-tanstack-start',
'name' => 'TanStack Start starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.',
'score' => 9, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png',
@@ -1012,7 +1030,7 @@ return [
[
'key' => 'starter-for-nuxt',
'name' => 'Nuxt starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Nuxt application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png',
@@ -1058,7 +1076,7 @@ return [
'name' => 'Event template',
'tagline' => 'Hackathon landing page with support for project submissions.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::EVENTS],
'useCases' => [SiteUseCases::EVENTS],
'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png',
'frameworks' => [
@@ -1096,7 +1114,7 @@ return [
'name' => 'Portfolio template',
'tagline' => 'Simple personal website to showcase your projects, articles, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png',
'frameworks' => [
@@ -1115,7 +1133,7 @@ return [
'name' => 'Store template',
'tagline' => 'E-commerce platform for selling products with Stripe integration.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::ECOMMERCE],
'useCases' => [SiteUseCases::ECOMMERCE],
'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png',
'frameworks' => [
@@ -1159,7 +1177,7 @@ return [
'name' => 'Blog template',
'tagline' => 'Platform for publishing written content and media.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'useCases' => [SiteUseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png',
'frameworks' => [
@@ -1178,7 +1196,7 @@ return [
'name' => 'Astro playground',
'tagline' => 'A basic Astro website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-astro-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-astro-light.png',
'frameworks' => [
@@ -1189,7 +1207,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1197,7 +1215,7 @@ return [
'name' => 'Remix playground',
'tagline' => 'A basic Remix website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-remix-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-remix-light.png',
'frameworks' => [
@@ -1208,7 +1226,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1216,7 +1234,7 @@ return [
'name' => 'Next.js playground',
'tagline' => 'A basic Next.js website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nextjs-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nextjs-light.png',
'frameworks' => [
@@ -1227,7 +1245,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1235,7 +1253,7 @@ return [
'name' => 'Flutter playground',
'tagline' => 'A basic Flutter website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-flutter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-flutter-light.png',
'frameworks' => [
@@ -1246,7 +1264,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1254,7 +1272,7 @@ return [
'name' => 'Vite playground',
'tagline' => 'A basic Vite website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vite-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vite-light.png',
'frameworks' => [
@@ -1265,7 +1283,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1273,7 +1291,7 @@ return [
'name' => 'Angular playground',
'tagline' => 'A basic Angular website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-angular-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-angular-light.png',
'frameworks' => [
@@ -1285,7 +1303,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1293,7 +1311,7 @@ return [
'name' => 'Analog playground',
'tagline' => 'A basic Analog website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-analog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-analog-light.png',
'frameworks' => [
@@ -1304,7 +1322,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1312,7 +1330,7 @@ return [
'name' => 'Svelte playground',
'tagline' => 'A basic Svelte website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-svelte-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-svelte-light.png',
'frameworks' => [
@@ -1323,7 +1341,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
@@ -1332,7 +1350,7 @@ return [
'name' => 'React playground',
'tagline' => 'A basic React website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-light.png',
'frameworks' => [
@@ -1344,7 +1362,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
@@ -1353,7 +1371,7 @@ return [
'name' => 'Vue playground',
'tagline' => 'A basic Vue website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vue-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vue-light.png',
'frameworks' => [
@@ -1364,7 +1382,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1372,7 +1390,7 @@ return [
'name' => 'Nuxt playground',
'tagline' => 'A basic Nuxt website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nuxt-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nuxt-light.png',
'frameworks' => [
@@ -1383,7 +1401,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1391,7 +1409,7 @@ return [
'name' => 'TanStack Start playground',
'tagline' => 'A basic TanStack Start website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-tanstack-start-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-tanstack-start-light.png',
'frameworks' => [
@@ -1402,7 +1420,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.5.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1410,7 +1428,7 @@ return [
'name' => 'React Native playground',
'tagline' => 'A basic React Native website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-native-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-native-light.png',
'frameworks' => [
@@ -1421,7 +1439,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1429,7 +1447,7 @@ return [
'name' => 'Lynx gallery',
'tagline' => 'A Lynx website showcasing gallery with smooth animations.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/gallery-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/gallery-for-lynx-light.png',
'frameworks' => [
@@ -1440,7 +1458,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -1448,7 +1466,7 @@ return [
'name' => 'Text-to-speech with ElevenLabs',
'tagline' => 'Next.js app that transforms text into natural, human-like speech using ElevenLabs',
'score' => 10, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::AI],
'useCases' => [SiteUseCases::AI],
'screenshotDark' => $url . '/images/sites/templates/text-to-speech-dark.png',
'screenshotLight' => $url . '/images/sites/templates/text-to-speech-light.png',
'frameworks' => [
@@ -1459,7 +1477,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.6.*',
'providerVersion' => '0.7.*',
'variables' => [
[
'name' => 'ELEVENLABS_API_KEY',
@@ -1476,7 +1494,7 @@ return [
'name' => 'CRM dashboard with React Admin',
'tagline' => 'A React-based admin dashboard template with CRM features.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DASHBOARD],
'useCases' => [SiteUseCases::DASHBOARD],
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
'frameworks' => [
@@ -1579,7 +1597,7 @@ return [
'name' => 'Job applications form with Formspree',
'tagline' => 'A simple form submission template using Formspree.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::FORMS],
'useCases' => [SiteUseCases::FORMS],
'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png',
'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png',
'frameworks' => [
+9
View File
@@ -1285,6 +1285,15 @@ return [
'category' => 'GraphQL',
'description' => '',
'variables' => [
[
'name' => '_APP_GRAPHQL_INTROSPECTION',
'description' => 'Enable or disable GraphQL introspection. Set to \'enabled\' to allow schema introspection, or \'disabled\' to block it. The default value is \'enabled\'.',
'introduction' => '',
'default' => 'enabled',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_GRAPHQL_MAX_BATCH_SIZE',
'description' => 'Maximum number of batched queries per request. The default value is 10.',
+47 -49
View File
@@ -33,11 +33,11 @@ use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit as EventAudit;
use Utopia\Audit\Audit;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code as ProofsCode;
use Utopia\Auth\Proofs\Password as ProofsPassword;
@@ -68,6 +68,7 @@ use Utopia\Validator;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -194,7 +195,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, arr
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@@ -959,6 +960,7 @@ App::post('/v1/account/sessions/email')
))
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->label('abuse-reset', [201])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('request')
@@ -1259,6 +1261,7 @@ App::post('/v1/account/sessions/token')
))
->label('abuse-limit', 10)
->label('abuse-key', 'ip:{ip},userId:{param-userId}')
->label('abuse-reset', [201])
->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('secret', '', new Text(256), 'Secret of a token generated by login methods. For example, the `createMagicURLToken` or `createPhoneToken` methods.')
->inject('request')
@@ -2298,7 +2301,7 @@ App::post('/v1/account/tokens/magic-url')
->setSubject($subject)
->setPreview($preview)
->setBody($body)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
if ($project->getId() === 'console') {
@@ -2599,7 +2602,7 @@ App::post('/v1/account/tokens/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@@ -2651,6 +2654,7 @@ App::put('/v1/account/sessions/magic-url')
))
->label('abuse-limit', 10)
->label('abuse-key', 'ip:{ip},userId:{param-userId}')
->label('abuse-reset', [201])
->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('secret', '', new Text(256), 'Valid verification token.')
->inject('request')
@@ -2904,26 +2908,21 @@ App::post('/v1/account/tokens/phone')
->setRecipients([$phone])
->setProviderType(MESSAGE_TYPE_SMS);
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
$helper = PhoneNumberUtil::getInstance();
try {
$countryCode = $helper->parse($phone)->getCountryCode();
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
} catch (NumberParseException $e) {
// Ignore invalid phone number for country code stats
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$token->setAttribute('secret', $secret);
@@ -2963,20 +2962,22 @@ App::post('/v1/account/jwts')
],
contentType: ContentType::JSON,
))
->label('abuse-limit', 100)
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('abuse-key', 'url:{url},userId:{userId}')
->inject('response')
->inject('user')
->inject('store')
->inject('proofForToken')
->action(function (Response $response, User $user, Store $store, ProofsToken $proofForToken) {
->action(function (int $duration, Response $response, User $user, Store $store, ProofsToken $proofForToken) {
$sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);
if (!$sessionId) {
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -3040,7 +3041,8 @@ App::get('/v1/account/logs')
->inject('locale')
->inject('geodb')
->inject('dbForProject')
->action(function (array $queries, bool $includeTotal, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) {
->inject('audit')
->action(function (array $queries, bool $includeTotal, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Audit $audit) {
try {
$queries = Query::parseQueries($queries);
@@ -3048,9 +3050,10 @@ App::get('/v1/account/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new EventAudit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), offset: $offset, limit: $limit);
$output = [];
@@ -3079,7 +3082,7 @@ App::get('/v1/account/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -3675,7 +3678,7 @@ App::post('/v1/account/recovery')
->setRecipient($profile->getAttribute('email', ''))
->setName($profile->getAttribute('name', ''))
->setBody($body)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setSubject($subject)
->setPreview($preview);
@@ -4006,7 +4009,7 @@ App::post('/v1/account/verifications/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name') ?? '');
@@ -4236,26 +4239,21 @@ App::post('/v1/account/verifications/phone')
->setRecipients([$user->getAttribute('phone')])
->setProviderType(MESSAGE_TYPE_SMS);
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
$helper = PhoneNumberUtil::getInstance();
try {
$countryCode = $helper->parse($phone)->getCountryCode();
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
} catch (NumberParseException $e) {
// Ignore invalid phone number for country code stats
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$verification->setAttribute('secret', $secret);
File diff suppressed because it is too large Load Diff
-147
View File
@@ -1,147 +0,0 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Domains\Domain;
use Utopia\System\System;
use Utopia\Validator\IP;
use Utopia\Validator\Text;
App::init()
->groups(['console'])
->inject('project')
->action(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
}
});
App::get('/v1/console/variables')
->desc('Get variables')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'console',
group: 'console',
name: 'variables',
description: '/docs/references/console/variables.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_CONSOLE_VARIABLES,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
$validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME'));
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
$validator = new IP(IP::V4);
$isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A')));
$validator = new IP(IP::V6);
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
$isDomainEnabled = $isAAAAValid || $isAValid || $isCNAMEValid;
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''));
$isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', ''));
$variables = new Document([
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
// Combine CAA domain with most common flags and tag (no parameters)
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
'_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'),
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
'_APP_VCS_ENABLED' => $isVcsEnabled,
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
]);
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
});
App::post('/v1/console/assistant')
->desc('Create assistant query')
->groups(['api', 'assistant'])
->label('scope', 'assistant.read')
->label('sdk', new Method(
namespace: 'assistant',
group: 'console',
name: 'chat',
description: '/docs/references/assistant/chat.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::TEXT
))
->label('abuse-limit', 15)
->label('abuse-key', 'userId:{userId}')
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
->inject('response')
->action(function (string $prompt, Response $response) {
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
$responseHeaders = [];
$query = json_encode(['prompt' => $prompt]);
$headers = ['accept: text/event-stream'];
$handleEvent = function ($ch, $data) use ($response) {
$response->chunk($data);
return \strlen($data);
};
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);
if (count($header) < 2) { // ignore invalid headers
return $len;
}
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
});
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
curl_exec($ch);
curl_close($ch);
$response->chunk('', true);
});
+4 -1
View File
@@ -224,8 +224,11 @@ function execute(
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
$validations = GraphQL::getStandardValidationRules();
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
if (System::getEnv('_APP_GRAPHQL_INTROSPECTION', 'enabled') === 'disabled') {
$validations[] = new DisableIntrospection();
}
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
$validations[] = new QueryComplexity($maxComplexity);
$validations[] = new QueryDepth($maxDepth);
}
File diff suppressed because it is too large Load Diff
+32 -16
View File
@@ -1147,7 +1147,8 @@ App::get('/v1/messaging/providers/:providerId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
@@ -1160,9 +1161,12 @@ App::get('/v1/messaging/providers/:providerId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'provider/' . $providerId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$output = [];
foreach ($logs as $i => &$log) {
@@ -1209,7 +1213,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2552,7 +2556,8 @@ App::get('/v1/messaging/topics/:topicId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$topic = $dbForProject->getDocument('topics', $topicId);
if ($topic->isEmpty()) {
@@ -2565,9 +2570,12 @@ App::get('/v1/messaging/topics/:topicId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'topic/' . $topicId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$output = [];
@@ -2615,7 +2623,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2968,7 +2976,8 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$subscriber = $dbForProject->getDocument('subscribers', $subscriberId);
if ($subscriber->isEmpty()) {
@@ -2981,9 +2990,12 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'subscriber/' . $subscriberId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
$output = [];
@@ -3031,7 +3043,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -3767,7 +3779,8 @@ App::get('/v1/messaging/messages/:messageId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@@ -3780,9 +3793,12 @@ App::get('/v1/messaging/messages/:messageId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'message/' . $messageId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
$output = [];
@@ -3830,7 +3846,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+26 -15
View File
@@ -21,6 +21,7 @@ use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
@@ -203,6 +204,7 @@ App::post('/v1/projects')
'accessedAt' => DateTime::now(),
'search' => implode(' ', [$projectId, $name]),
'database' => $dsn,
'labels' => [],
]));
} catch (Duplicate) {
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
@@ -225,6 +227,7 @@ App::post('/v1/projects')
if (!$sharedTablesV2) {
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDatabase(APP_DATABASE);
if ($sharedTables) {
$dbForProject
@@ -247,13 +250,15 @@ App::post('/v1/projects')
}
if ($create || $projectTables) {
$audit = new Audit($dbForProject);
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
$audit->setup();
}
if (!$create && $sharedTablesV1) {
$attributes = \array_map(fn ($attribute) => new Document($attribute), Audit::ATTRIBUTES);
$indexes = \array_map(fn (array $index) => new Document($index), Audit::INDEXES);
$adapter = new AdapterDatabase($dbForProject);
$attributes = $adapter->getAttributeDocuments();
$indexes = $adapter->getIndexDocuments();
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom('audit'),
'$permissions' => [Permission::create(Role::any())],
@@ -294,7 +299,7 @@ App::post('/v1/projects')
// Hook allowing instant project mirroring during migration
// Outside of migration, hook is not registered and has no effect
$hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]);
$hooks->trigger('afterProjectCreation', [$project, $pools, $cache]);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -1478,7 +1483,7 @@ App::post('/v1/projects/:projectId/keys')
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForPlatform')
@@ -1497,8 +1502,9 @@ App::post('/v1/projects/:projectId/keys')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
'name' => $name,
'scopes' => $scopes,
'expire' => $expire,
@@ -1546,7 +1552,8 @@ App::get('/v1/projects/:projectId/keys')
}
$keys = $dbForPlatform->find('keys', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
Query::limit(5000),
]);
@@ -1587,7 +1594,8 @@ App::get('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
]);
if ($key->isEmpty()) {
@@ -1617,7 +1625,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForPlatform')
@@ -1631,7 +1639,8 @@ App::put('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
]);
if ($key->isEmpty()) {
@@ -1682,7 +1691,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
]);
if ($key->isEmpty()) {
@@ -1716,7 +1726,7 @@ App::post('/v1/projects/:projectId/jwts')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForPlatform')
@@ -2079,6 +2089,7 @@ App::patch('/v1/projects/:projectId/smtp')
if ($enabled) {
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->SMTPAuth = (!empty($username) && !empty($password));
$mail->Username = $username;
$mail->Password = $password;
$mail->Host = $host;
@@ -2094,7 +2105,7 @@ App::patch('/v1/projects/:projectId/smtp')
throw new Exception('Connection is not valid.');
}
} catch (Throwable $error) {
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, 'Could not connect to SMTP server: ' . $error->getMessage());
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, $error->getMessage());
}
}
@@ -2665,7 +2676,7 @@ App::patch('/v1/projects/:projectId/auth/session-invalidation')
$auths = $project->getAttribute('auths', []);
$auths['invalidateSessions'] = $enabled;
$dbForPlatform->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
File diff suppressed because it is too large Load Diff
+31 -24
View File
@@ -23,9 +23,9 @@ use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Teams;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Proofs\Password;
@@ -103,6 +103,7 @@ App::post('/v1/teams')
Permission::update(Role::team($teamId, 'owner')),
Permission::delete(Role::team($teamId, 'owner')),
],
'labels' => [],
'name' => $name,
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
'prefs' => new \stdClass(),
@@ -432,15 +433,22 @@ App::delete('/v1/teams/:teamId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
}
// Sync delete
$deletes = new Deletes();
$deletes->deleteMemberships($getProjectDB, $team, $project);
// Async delete
if ($project->getId() === 'console') {
$queueForDeletes
->setType(DELETE_TYPE_TEAM_PROJECTS)
->setDocument($team);
->setDocument($team)
->trigger();
}
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($team);
$queueForEvents
->setParam('teamId', $team->getId())
->setPayload($response->output($team, Response::MODEL_TEAM))
@@ -771,7 +779,7 @@ App::post('/v1/teams/:teamId/memberships')
->setPreview($preview)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name', ''))
->setVariables($emailVariables)
->appendVariables($emailVariables)
->trigger();
} elseif (!empty($phone)) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
@@ -801,26 +809,21 @@ App::post('/v1/teams/:teamId/memberships')
->setRecipients([$phone])
->setProviderType('SMS');
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
$helper = PhoneNumberUtil::getInstance();
try {
$countryCode = $helper->parse($phone)->getCountryCode();
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
} catch (NumberParseException $e) {
// Ignore invalid phone number for country code stats
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
}
@@ -1471,7 +1474,8 @@ App::get('/v1/teams/:teamId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -1485,9 +1489,12 @@ App::get('/v1/teams/:teamId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'team/' . $team->getId();
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$output = [];
@@ -1534,7 +1541,7 @@ App::get('/v1/teams/:teamId/logs')
}
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+8 -4
View File
@@ -945,7 +945,8 @@ App::get('/v1/users/:userId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$user = $dbForProject->getDocument('users', $userId);
@@ -958,8 +959,11 @@ App::get('/v1/users/:userId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), limit: $limit, offset: $offset);
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
@@ -999,7 +1003,7 @@ App::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+1
View File
@@ -306,6 +306,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'resourceType' => $resourceCollection,
'entrypoint' => $resource->getAttribute('entrypoint', ''),
'buildCommands' => \implode(' && ', $commands),
'startCommand' => $resource->getAttribute('startCommand', ''),
'buildOutput' => $resource->getAttribute('outputDirectory', ''),
'adapter' => $resource->getAttribute('adapter', ''),
'fallbackFile' => $resource->getAttribute('fallbackFile', ''),
+172 -74
View File
@@ -6,6 +6,7 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Key;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\StatsUsage;
@@ -59,7 +60,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey)
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
{
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
@@ -802,6 +803,20 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
->setProject($project)
->trigger();
/* cleanup */
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
$resourceType = $type === 'function'
? RESOURCE_TYPE_FUNCTIONS
: RESOURCE_TYPE_SITES;
$queueForDeletes
->setProject($project)
->setResourceType($resourceType)
->setResource($resource->getSequence())
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
->trigger();
}
return true;
} elseif ($type === 'api') {
return false;
@@ -812,8 +827,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
} else {
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type, view: $errorView);
}
return false;
}
App::init()
@@ -863,7 +876,9 @@ App::init()
->inject('apiKey')
->inject('cors')
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization) {
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
/*
* Appwrite Router
*/
@@ -871,7 +886,7 @@ App::init()
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1034,7 +1049,7 @@ App::init()
->inject('dbForPlatform')
->inject('queueForCertificates')
->inject('platform')
->inject('authorization')
->inject('authorization')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) {
$hostname = $request->getHostname();
$cache = Config::getParam('hostnames', []);
@@ -1050,80 +1065,77 @@ App::init()
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$cache[$domain->get()] = false;
Config::setParam('hostnames', $cache);
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
return;
}
if (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
return;
}
// 3. Check if domain is a main domain
if (!in_array($domain->get(), $platformHostnames)) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
return;
}
// 4. Check/create rule (requires DB access)
$authorization->disable();
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$document = $isMd5
? $dbForPlatform->getDocument('rules', md5($domain->get()))
: $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain->get()]),
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, &$cache) {
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$document = $isMd5
? $dbForPlatform->getDocument('rules', md5($domain->get()))
: $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain->get()]),
]);
if (!$document->isEmpty()) {
return;
}
// 5. Create new rule
$owner = '';
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
$funcDomain = $fallback;
}
if (
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
) {
$owner = 'Appwrite';
}
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
$document = new Document([
'$id' => $ruleId,
'domain' => $domain->get(),
'type' => 'api',
'status' => 'verifying',
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'search' => implode(' ', [$ruleId, $domain->get()]),
'owner' => $owner,
'region' => $console->getAttribute('region')
]);
if (!$document->isEmpty()) {
return;
$dbForPlatform->createDocument('rules', $document);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$queueForCertificates
->setDomain($document)
->setSkipRenewCheck(true)
->trigger();
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
}
// 5. Create new rule
$owner = '';
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
$funcDomain = $fallback;
}
if (
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
) {
$owner = 'Appwrite';
}
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
$document = new Document([
'$id' => $ruleId,
'domain' => $domain->get(),
'type' => 'api',
'status' => 'verifying',
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'search' => implode(' ', [$ruleId, $domain->get()]),
'owner' => $owner,
'region' => $console->getAttribute('region')
]);
$dbForPlatform->createDocument('rules', $document);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$queueForCertificates
->setDomain($document)
->setSkipRenewCheck(true)
->trigger();
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
$authorization->reset();
}
});
});
App::options()
@@ -1147,14 +1159,16 @@ App::options()
->inject('apiKey')
->inject('cors')
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization) {
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
/*
* Appwrite Router
*/
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1214,7 +1228,7 @@ App::error()
}
switch ($class) {
case 'Utopia\Exception':
case Utopia\Exception::class:
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
switch ($code) {
case 400:
@@ -1225,10 +1239,10 @@ App::error()
break;
}
break;
case 'Utopia\Database\Exception\Authorization':
case Utopia\Database\Exception\Authorization::class:
$error = new AppwriteException(AppwriteException::USER_UNAUTHORIZED);
break;
case 'Utopia\Database\Exception\Timeout':
case Utopia\Database\Exception\Timeout::class:
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
break;
}
@@ -1335,6 +1349,86 @@ App::error()
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', $authorization->getRoles());
try {
/* add queries to log */
$queries = $request->getParam('queries', []);
if (!empty($queries) && is_array($queries)) {
$parsedQueries = Query::parseQueries($queries);
// format query by removing sensitive values
$formatQuery = function (array $queryArray) use (&$formatQuery): ?array {
$method = $queryArray['method'] ?? '';
$values = $queryArray['values'] ?? [];
$attribute = $queryArray['attribute'] ?? '';
if (!is_string($method) || $method === '') {
return null;
}
// logical queries - recursively format nested queries
if (in_array($method, [Query::TYPE_AND, Query::TYPE_OR], true)) {
$nested = [];
foreach ($values as $nestedArray) {
if (is_array($nestedArray)) {
$formatted = $formatQuery($nestedArray);
if ($formatted !== null) {
$nested[] = $formatted;
}
}
}
return empty($nested) ? null : [$method => $nested];
}
// select - show selected attributes
if ($method === Query::TYPE_SELECT) {
$attributes = array_values(array_filter($values, 'is_string'));
return [$method => $attributes];
}
// pagination
if (in_array($method, [
Query::TYPE_LIMIT,
Query::TYPE_OFFSET,
Query::TYPE_CURSOR_AFTER,
Query::TYPE_CURSOR_BEFORE
], true)) {
return [$method => []];
}
// orders
if (in_array($method, [
Query::TYPE_ORDER_DESC,
Query::TYPE_ORDER_ASC,
Query::TYPE_ORDER_RANDOM
], true)) {
return [$method => !empty($attribute) ? [$attribute] : []];
}
// filter
if (!empty($attribute)) {
return [$method => [$attribute]];
}
// fallback
return [$method => []];
};
$formattedQueries = [];
foreach ($parsedQueries as $query) {
$formatted = $formatQuery($query->toArray());
if ($formatted !== null) {
$formattedQueries[] = $formatted;
}
}
if (!empty($formattedQueries)) {
$log->addExtra('queries', $formattedQueries);
}
}
} catch (Throwable $_) {
// don't fail the error handler
}
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
if (!empty($sdk)) {
/** @var \Appwrite\SDK\Method $sdk */
@@ -1409,7 +1503,7 @@ App::error()
$template = $error->getView() ?? (($route) ? $route->getLabel('error', null) : null);
// TODO: Ideally use group 'api' here, but all wildcard routes seem to have 'api' at the moment
if (!\str_starts_with($route->getPath(), '/v1')) {
if (empty($route) || !\str_starts_with($route->getPath(), '/v1')) {
$template = __DIR__ . '/../views/general/error.phtml';
}
@@ -1458,13 +1552,15 @@ App::get('/robots.txt')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization) {
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1491,13 +1587,15 @@ App::get('/humans.txt')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization) {
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
+4 -3
View File
@@ -191,7 +191,7 @@ App::post('/v1/mock/api-key-unprefixed')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$scopes = array_keys(Config::getParam('scopes'));
$scopes = array_keys(Config::getParam('projectScopes'));
$key = new Document([
'$id' => ID::unique(),
@@ -200,8 +200,9 @@ App::post('/v1/mock/api-key-unprefixed')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
'name' => 'Outdated key',
'scopes' => $scopes,
'expire' => null,
+109 -205
View File
@@ -10,12 +10,12 @@ use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\EventProcessor;
use Appwrite\SDK\Method;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
@@ -31,7 +31,6 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Queue\Publisher;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Validator\WhiteList;
@@ -75,151 +74,6 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
return $label;
};
/**
* This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**.
*
* Accounts can be created in many ways beyond `createAccount`
* (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.
*/
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
// Only trigger events for user creation with the database listener.
if ($document->getCollection() !== 'users') {
return;
}
$queueForEvents
->setEvent('users.[userId].create')
->setParam('userId', $document->getId())
->setPayload($response->output($document, Response::MODEL_USER));
// Trigger functions, webhooks, and realtime events
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger webhooks events only if a project has them enabled */
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
/** Trigger realtime events only for non console events */
if ($queueForEvents->getProject()->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
};
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$value = 1;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
}
switch (true) {
case $document->getCollection() === 'teams':
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
break;
case $document->getCollection() === 'users':
$queueForStatsUsage->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case $document->getCollection() === 'sessions': // sessions
$queueForStatsUsage->addMetric(METRIC_SESSIONS, $value); //per project
break;
case $document->getCollection() === 'databases': // databases
$queueForStatsUsage->addMetric(METRIC_DATABASES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value);
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_DOCUMENTS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
break;
case $document->getCollection() === 'buckets': //buckets
$queueForStatsUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$queueForStatsUsage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$queueForStatsUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'sites':
$queueForStatsUsage
->addMetric(METRIC_SITES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$queueForStatsUsage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
break;
default:
break;
}
};
App::init()
->groups(['api'])
->inject('utopia')
@@ -303,10 +157,6 @@ App::init()
// Step 5: API Key Authentication
if (!empty($apiKey)) {
// Verify no user session exists simultaneously
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if key is expired
if ($apiKey->isExpired()) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
@@ -335,23 +185,38 @@ App::init()
}
// For standard keys, update last accessed time
if ($apiKey->getType() === API_KEY_STANDARD) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
if (\in_array($apiKey->getType(), [API_KEY_STANDARD, API_KEY_ORGANIZATION, API_KEY_ACCOUNT])) {
$dbKey = null;
if (!empty($apiKey->getProjectId())) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getUserId())) {
$dbKey = $user->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getTeamId())) {
$dbKey = $team->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
}
if (!$dbKey) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$updates = new Document();
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$dbKey->setAttribute('accessedAt', DateTime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
$updates->setAttribute('accessedAt', DateTime::now());
}
$sdkValidator = new WhiteList($servers, true);
@@ -362,12 +227,21 @@ App::init()
if (!in_array($sdk, $sdks)) {
$sdks[] = $sdk;
$dbKey->setAttribute('sdks', $sdks);
/** Update access time as well */
$dbKey->setAttribute('accessedAt', Datetime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
$updates->setAttribute('sdks', $sdks);
$updates->setAttribute('accessedAt', Datetime::now());
}
}
if (!$updates->isEmpty()) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('keys', $dbKey->getId(), $updates));
if (!empty($apiKey->getProjectId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
} elseif (!empty($apiKey->getUserId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('users', $user->getId()));
} elseif (!empty($apiKey->getTeamId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId()));
}
}
@@ -489,9 +363,6 @@ App::init()
->inject('response')
->inject('project')
->inject('user')
->inject('publisher')
->inject('publisherFunctions')
->inject('publisherWebhooks')
->inject('queueForEvents')
->inject('queueForMessaging')
->inject('queueForAudits')
@@ -501,7 +372,6 @@ App::init()
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForMails')
->inject('queueForMigrations')
->inject('dbForProject')
->inject('timelimit')
->inject('resourceToken')
@@ -512,7 +382,7 @@ App::init()
->inject('telemetry')
->inject('platform')
->inject('authorization')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Migration $queueForMigrations, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) use ($usageDatabaseListener, $eventDatabaseListener) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
$route = $utopia->getRoute();
@@ -632,28 +502,6 @@ App::init()
$queueForBuilds->setPlatform($platform);
$queueForMails->setPlatform($platform);
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.
$queueForEventsClone = new Event($publisher);
$queueForFunctions = new Func($publisherFunctions);
$queueForWebhooks = new Webhook($publisherWebhooks);
$queueForRealtime = new Realtime();
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
$response,
$queueForEventsClone->from($queueForEvents),
$queueForFunctions->from($queueForEvents),
$queueForWebhooks->from($queueForEvents),
$queueForRealtime->from($queueForEvents)
));
$useCache = $route->getLabel('cache', false);
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
@@ -817,7 +665,9 @@ App::shutdown()
->inject('queueForRealtime')
->inject('dbForProject')
->inject('authorization')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization) use ($parseLabel) {
->inject('timelimit')
->inject('eventProcessor')
->action(function (App $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -826,9 +676,15 @@ App::shutdown()
$queueForEvents->setPayload($responsePayload);
}
$queueForFunctions
->from($queueForEvents)
->trigger();
// Get project and function/webhook events (cached)
$functionsEvents = $eventProcessor->getFunctionsEvents($project, $dbForProject);
$webhooksEvents = $eventProcessor->getWebhooksEvents($project);
// Generate events for this operation
$generatedEvents = Event::generateEvents(
$queueForEvents->getEvent(),
$queueForEvents->getParams()
);
if ($project->getId() !== 'console') {
$queueForRealtime
@@ -836,21 +692,69 @@ App::shutdown()
->trigger();
}
/** Trigger webhooks events only if a project has them enabled
* A future optimisation is to only trigger webhooks if the webhook is "enabled"
* But it might have performance implications on the API due to the number of webhooks etc.
* Some profiling is needed to see if this is a problem.
*/
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
// Only trigger functions if there are matching function events
if (!empty($functionsEvents)) {
foreach ($generatedEvents as $event) {
if (isset($functionsEvents[$event])) {
$queueForFunctions
->from($queueForEvents)
->trigger();
break;
}
}
}
// Only trigger webhooks if there are matching webhook events
if (!empty($webhooksEvents)) {
foreach ($generatedEvents as $event) {
if (isset($webhooksEvents[$event])) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
break;
}
}
}
}
$route = $utopia->getRoute();
$requestParams = $route->getParamsValues();
/**
* Abuse labels
*/
$abuseEnabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
$abuseResetCode = $route->getLabel('abuse-reset', []);
$abuseResetCode = \is_array($abuseResetCode) ? $abuseResetCode : [$abuseResetCode];
if ($abuseEnabled && \count($abuseResetCode) > 0 && \in_array($response->getStatusCode(), $abuseResetCode)) {
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($abuseKeyLabel as $abuseKey) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
$timeLimit
->setParam('{projectId}', $project->getId())
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath())
->setParam('{method}', $request->getMethod())
->setParam('{chunkId}', (int)($start / ($end + 1 - $start)));
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (!empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
$abuse->reset();
}
}
/**
* Audit labels
*/
-43
View File
@@ -1,43 +0,0 @@
<?php
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
App::init()
->groups(['web'])
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$response
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
});
App::get('/')
->alias('auth/*')
->alias('/invite')
->alias('/login')
->alias('/mfa')
->alias('/card/*')
->alias('/recover')
->alias('/register/*')
->groups(['web'])
->label('permission', 'public')
->label('scope', 'home')
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$url = parse_url($request->getURI());
$target = "/console{$url['path']}";
$params = $request->getParams();
if (!empty($params)) {
$target .= "?" . \http_build_query($params);
}
if ($url['fragment'] ?? false) {
$target .= "#{$url['fragment']}";
}
$response->redirect($target);
});
+8 -4
View File
@@ -12,6 +12,8 @@ use Swoole\Process;
use Swoole\Table;
use Swoole\Timer;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Adapter\SQL as AuditAdapterSQL;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Compression\Compression;
@@ -261,8 +263,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
$authorization = $app->getResource('authorization');
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForPlatform);
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForPlatform);
$audit = new Audit($adapter);
$audit->setup();
}
@@ -390,8 +393,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
Console::success('[Setup] - Skip: metadata table already exists');
}
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForProject);
if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
$audit->setup();
}
+1
View File
@@ -26,6 +26,7 @@ require_once __DIR__ . '/init/database/filters.php';
require_once __DIR__ . '/init/database/formats.php';
require_once __DIR__ . '/init/locales.php';
require_once __DIR__ . '/init/registers.php';
require_once __DIR__ . '/init/models.php';
require_once __DIR__ . '/init/resources.php';
\stream_context_set_default([ // Set global user agent and http settings
+3 -1
View File
@@ -22,7 +22,9 @@ Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapt
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
Config::load('projectScopes', __DIR__ . '/../config/scopes/project.php', $configAdapter);
Config::load('organizationScopes', __DIR__ . '/../config/scopes/organization.php', $configAdapter);
Config::load('accountScopes', __DIR__ . '/../config/scopes/account.php', $configAdapter);
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
+11 -2
View File
@@ -44,7 +44,7 @@ const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4321;
const APP_VERSION_STABLE = '1.8.0';
const APP_VERSION_STABLE = '1.8.1';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -161,6 +161,9 @@ const ACTIVITY_TYPE_GUEST = 'guest';
const MFA_RECENT_DURATION = 1800; // 30 mins
// Database name
const APP_DATABASE = 'appwrite';
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
@@ -178,8 +181,10 @@ const BUILD_TYPE_DEPLOYMENT = 'deployment';
const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const DELETE_TYPE_DATABASES = 'databases';
const ENABLE_EXECUTIONS_LIMIT_ON_ROUTE = false;
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_TRANSACTION = 'transaction';
@@ -189,8 +194,10 @@ const DELETE_TYPE_SITES = 'sites';
const DELETE_TYPE_FUNCTIONS = 'functions';
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
const DELETE_TYPE_USERS = 'users';
const DELETE_TYPE_TEAMS = 'teams';
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_EXECUTIONS_LIMIT = 'executionsLimit';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_USAGE = 'usage';
@@ -243,6 +250,8 @@ const MESSAGE_TYPE_PUSH = 'push';
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_DYNAMIC = 'dynamic';
const API_KEY_ORGANIZATION = 'organization';
const API_KEY_ACCOUNT = 'account';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
+33 -1
View File
@@ -135,7 +135,8 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
@@ -432,3 +433,34 @@ Database::addFilter(
return $value;
}
);
Database::addFilter(
'subQueryOrganizationKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['teams']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryAccountKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['users']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
+360
View File
@@ -0,0 +1,360 @@
<?php
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Account;
use Appwrite\Utopia\Response\Model\AlgoArgon2;
use Appwrite\Utopia\Response\Model\AlgoBcrypt;
use Appwrite\Utopia\Response\Model\AlgoMd5;
use Appwrite\Utopia\Response\Model\AlgoPhpass;
use Appwrite\Utopia\Response\Model\AlgoScrypt;
use Appwrite\Utopia\Response\Model\AlgoScryptModified;
use Appwrite\Utopia\Response\Model\AlgoSha;
use Appwrite\Utopia\Response\Model\Any;
use Appwrite\Utopia\Response\Model\Attribute;
use Appwrite\Utopia\Response\Model\AttributeBoolean;
use Appwrite\Utopia\Response\Model\AttributeDatetime;
use Appwrite\Utopia\Response\Model\AttributeEmail;
use Appwrite\Utopia\Response\Model\AttributeEnum;
use Appwrite\Utopia\Response\Model\AttributeFloat;
use Appwrite\Utopia\Response\Model\AttributeInteger;
use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeLine;
use Appwrite\Utopia\Response\Model\AttributeList;
use Appwrite\Utopia\Response\Model\AttributeLongtext;
use Appwrite\Utopia\Response\Model\AttributeMediumtext;
use Appwrite\Utopia\Response\Model\AttributePoint;
use Appwrite\Utopia\Response\Model\AttributePolygon;
use Appwrite\Utopia\Response\Model\AttributeRelationship;
use Appwrite\Utopia\Response\Model\AttributeString;
use Appwrite\Utopia\Response\Model\AttributeText;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\AttributeVarchar;
use Appwrite\Utopia\Response\Model\AuthProvider;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Branch;
use Appwrite\Utopia\Response\Model\Bucket;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Column;
use Appwrite\Utopia\Response\Model\ColumnBoolean;
use Appwrite\Utopia\Response\Model\ColumnDatetime;
use Appwrite\Utopia\Response\Model\ColumnEmail;
use Appwrite\Utopia\Response\Model\ColumnEnum;
use Appwrite\Utopia\Response\Model\ColumnFloat;
use Appwrite\Utopia\Response\Model\ColumnIndex;
use Appwrite\Utopia\Response\Model\ColumnInteger;
use Appwrite\Utopia\Response\Model\ColumnIP;
use Appwrite\Utopia\Response\Model\ColumnLine;
use Appwrite\Utopia\Response\Model\ColumnList;
use Appwrite\Utopia\Response\Model\ColumnLongtext;
use Appwrite\Utopia\Response\Model\ColumnMediumtext;
use Appwrite\Utopia\Response\Model\ColumnPoint;
use Appwrite\Utopia\Response\Model\ColumnPolygon;
use Appwrite\Utopia\Response\Model\ColumnRelationship;
use Appwrite\Utopia\Response\Model\ColumnString;
use Appwrite\Utopia\Response\Model\ColumnText;
use Appwrite\Utopia\Response\Model\ColumnURL;
use Appwrite\Utopia\Response\Model\ColumnVarchar;
use Appwrite\Utopia\Response\Model\ConsoleVariables;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
use Appwrite\Utopia\Response\Model\Currency;
use Appwrite\Utopia\Response\Model\Database;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Utopia\Response\Model\DetectionFramework;
use Appwrite\Utopia\Response\Model\DetectionRuntime;
use Appwrite\Utopia\Response\Model\DetectionVariable;
use Appwrite\Utopia\Response\Model\DevKey;
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
use Appwrite\Utopia\Response\Model\Error;
use Appwrite\Utopia\Response\Model\ErrorDev;
use Appwrite\Utopia\Response\Model\Execution;
use Appwrite\Utopia\Response\Model\File;
use Appwrite\Utopia\Response\Model\Framework;
use Appwrite\Utopia\Response\Model\FrameworkAdapter;
use Appwrite\Utopia\Response\Model\Func;
use Appwrite\Utopia\Response\Model\Headers;
use Appwrite\Utopia\Response\Model\HealthAntivirus;
use Appwrite\Utopia\Response\Model\HealthCertificate;
use Appwrite\Utopia\Response\Model\HealthQueue;
use Appwrite\Utopia\Response\Model\HealthStatus;
use Appwrite\Utopia\Response\Model\HealthTime;
use Appwrite\Utopia\Response\Model\HealthVersion;
use Appwrite\Utopia\Response\Model\Identity;
use Appwrite\Utopia\Response\Model\Index;
use Appwrite\Utopia\Response\Model\Installation;
use Appwrite\Utopia\Response\Model\JWT;
use Appwrite\Utopia\Response\Model\Key;
use Appwrite\Utopia\Response\Model\Language;
use Appwrite\Utopia\Response\Model\Locale;
use Appwrite\Utopia\Response\Model\LocaleCode;
use Appwrite\Utopia\Response\Model\Log;
use Appwrite\Utopia\Response\Model\Membership;
use Appwrite\Utopia\Response\Model\Message;
use Appwrite\Utopia\Response\Model\Metric;
use Appwrite\Utopia\Response\Model\MetricBreakdown;
use Appwrite\Utopia\Response\Model\MFAChallenge;
use Appwrite\Utopia\Response\Model\MFAFactors;
use Appwrite\Utopia\Response\Model\MFARecoveryCodes;
use Appwrite\Utopia\Response\Model\MFAType;
use Appwrite\Utopia\Response\Model\Migration;
use Appwrite\Utopia\Response\Model\MigrationFirebaseProject;
use Appwrite\Utopia\Response\Model\MigrationReport;
use Appwrite\Utopia\Response\Model\Mock;
use Appwrite\Utopia\Response\Model\MockNumber;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\Phone;
use Appwrite\Utopia\Response\Model\Platform;
use Appwrite\Utopia\Response\Model\Preferences;
use Appwrite\Utopia\Response\Model\Project;
use Appwrite\Utopia\Response\Model\Provider;
use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
use Appwrite\Utopia\Response\Model\ResourceToken;
use Appwrite\Utopia\Response\Model\Row;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Session;
use Appwrite\Utopia\Response\Model\Site;
use Appwrite\Utopia\Response\Model\Specification;
use Appwrite\Utopia\Response\Model\Subscriber;
use Appwrite\Utopia\Response\Model\Table;
use Appwrite\Utopia\Response\Model\Target;
use Appwrite\Utopia\Response\Model\Team;
use Appwrite\Utopia\Response\Model\TemplateEmail;
use Appwrite\Utopia\Response\Model\TemplateFramework;
use Appwrite\Utopia\Response\Model\TemplateFunction;
use Appwrite\Utopia\Response\Model\TemplateRuntime;
use Appwrite\Utopia\Response\Model\TemplateSite;
use Appwrite\Utopia\Response\Model\TemplateSMS;
use Appwrite\Utopia\Response\Model\TemplateVariable;
use Appwrite\Utopia\Response\Model\Token;
use Appwrite\Utopia\Response\Model\Topic;
use Appwrite\Utopia\Response\Model\Transaction;
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
use Appwrite\Utopia\Response\Model\UsageDatabase;
use Appwrite\Utopia\Response\Model\UsageDatabases;
use Appwrite\Utopia\Response\Model\UsageFunction;
use Appwrite\Utopia\Response\Model\UsageFunctions;
use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageSite;
use Appwrite\Utopia\Response\Model\UsageSites;
use Appwrite\Utopia\Response\Model\UsageStorage;
use Appwrite\Utopia\Response\Model\UsageTable;
use Appwrite\Utopia\Response\Model\UsageUsers;
use Appwrite\Utopia\Response\Model\User;
use Appwrite\Utopia\Response\Model\Variable;
use Appwrite\Utopia\Response\Model\VcsContent;
use Appwrite\Utopia\Response\Model\Webhook;
// General
Response::setModel(new None());
Response::setModel(new Any());
Response::setModel(new Error());
Response::setModel(new ErrorDev());
// Lists
Response::setModel(new BaseList('Rows List', Response::MODEL_ROW_LIST, 'rows', Response::MODEL_ROW));
Response::setModel(new BaseList('Documents List', Response::MODEL_DOCUMENT_LIST, 'documents', Response::MODEL_DOCUMENT));
Response::setModel(new BaseList('Tables List', Response::MODEL_TABLE_LIST, 'tables', Response::MODEL_TABLE));
Response::setModel(new BaseList('Collections List', Response::MODEL_COLLECTION_LIST, 'collections', Response::MODEL_COLLECTION));
Response::setModel(new BaseList('Databases List', Response::MODEL_DATABASE_LIST, 'databases', Response::MODEL_DATABASE));
Response::setModel(new BaseList('Indexes List', Response::MODEL_INDEX_LIST, 'indexes', Response::MODEL_INDEX));
Response::setModel(new BaseList('Column Indexes List', Response::MODEL_COLUMN_INDEX_LIST, 'indexes', Response::MODEL_COLUMN_INDEX));
Response::setModel(new BaseList('Users List', Response::MODEL_USER_LIST, 'users', Response::MODEL_USER));
Response::setModel(new BaseList('Sessions List', Response::MODEL_SESSION_LIST, 'sessions', Response::MODEL_SESSION));
Response::setModel(new BaseList('Identities List', Response::MODEL_IDENTITY_LIST, 'identities', Response::MODEL_IDENTITY));
Response::setModel(new BaseList('Logs List', Response::MODEL_LOG_LIST, 'logs', Response::MODEL_LOG));
Response::setModel(new BaseList('Files List', Response::MODEL_FILE_LIST, 'files', Response::MODEL_FILE));
Response::setModel(new BaseList('Buckets List', Response::MODEL_BUCKET_LIST, 'buckets', Response::MODEL_BUCKET));
Response::setModel(new BaseList('Resource Tokens List', Response::MODEL_RESOURCE_TOKEN_LIST, 'tokens', Response::MODEL_RESOURCE_TOKEN));
Response::setModel(new BaseList('Teams List', Response::MODEL_TEAM_LIST, 'teams', Response::MODEL_TEAM));
Response::setModel(new BaseList('Memberships List', Response::MODEL_MEMBERSHIP_LIST, 'memberships', Response::MODEL_MEMBERSHIP));
Response::setModel(new BaseList('Sites List', Response::MODEL_SITE_LIST, 'sites', Response::MODEL_SITE));
Response::setModel(new BaseList('Site Templates List', Response::MODEL_TEMPLATE_SITE_LIST, 'templates', Response::MODEL_TEMPLATE_SITE));
Response::setModel(new BaseList('Functions List', Response::MODEL_FUNCTION_LIST, 'functions', Response::MODEL_FUNCTION));
Response::setModel(new BaseList('Function Templates List', Response::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', Response::MODEL_TEMPLATE_FUNCTION));
Response::setModel(new BaseList('Installations List', Response::MODEL_INSTALLATION_LIST, 'installations', Response::MODEL_INSTALLATION));
Response::setModel(new BaseList('Framework Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK));
Response::setModel(new BaseList('Runtime Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME));
Response::setModel(new BaseList('Branches List', Response::MODEL_BRANCH_LIST, 'branches', Response::MODEL_BRANCH));
Response::setModel(new BaseList('Frameworks List', Response::MODEL_FRAMEWORK_LIST, 'frameworks', Response::MODEL_FRAMEWORK));
Response::setModel(new BaseList('Runtimes List', Response::MODEL_RUNTIME_LIST, 'runtimes', Response::MODEL_RUNTIME));
Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_LIST, 'deployments', Response::MODEL_DEPLOYMENT));
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
Response::setModel(new BaseList('Platforms List', Response::MODEL_PLATFORM_LIST, 'platforms', Response::MODEL_PLATFORM, true, false));
Response::setModel(new BaseList('Countries List', Response::MODEL_COUNTRY_LIST, 'countries', Response::MODEL_COUNTRY));
Response::setModel(new BaseList('Continents List', Response::MODEL_CONTINENT_LIST, 'continents', Response::MODEL_CONTINENT));
Response::setModel(new BaseList('Languages List', Response::MODEL_LANGUAGE_LIST, 'languages', Response::MODEL_LANGUAGE));
Response::setModel(new BaseList('Currencies List', Response::MODEL_CURRENCY_LIST, 'currencies', Response::MODEL_CURRENCY));
Response::setModel(new BaseList('Phones List', Response::MODEL_PHONE_LIST, 'phones', Response::MODEL_PHONE));
Response::setModel(new BaseList('Metric List', Response::MODEL_METRIC_LIST, 'metrics', Response::MODEL_METRIC, true, false));
Response::setModel(new BaseList('Variables List', Response::MODEL_VARIABLE_LIST, 'variables', Response::MODEL_VARIABLE));
Response::setModel(new BaseList('Status List', Response::MODEL_HEALTH_STATUS_LIST, 'statuses', Response::MODEL_HEALTH_STATUS));
Response::setModel(new BaseList('Rule List', Response::MODEL_PROXY_RULE_LIST, 'rules', Response::MODEL_PROXY_RULE));
Response::setModel(new BaseList('Locale codes list', Response::MODEL_LOCALE_CODE_LIST, 'localeCodes', Response::MODEL_LOCALE_CODE));
Response::setModel(new BaseList('Provider list', Response::MODEL_PROVIDER_LIST, 'providers', Response::MODEL_PROVIDER));
Response::setModel(new BaseList('Message list', Response::MODEL_MESSAGE_LIST, 'messages', Response::MODEL_MESSAGE));
Response::setModel(new BaseList('Topic list', Response::MODEL_TOPIC_LIST, 'topics', Response::MODEL_TOPIC));
Response::setModel(new BaseList('Subscriber list', Response::MODEL_SUBSCRIBER_LIST, 'subscribers', Response::MODEL_SUBSCRIBER));
Response::setModel(new BaseList('Target list', Response::MODEL_TARGET_LIST, 'targets', Response::MODEL_TARGET));
Response::setModel(new BaseList('Transaction List', Response::MODEL_TRANSACTION_LIST, 'transactions', Response::MODEL_TRANSACTION));
Response::setModel(new BaseList('Migrations List', Response::MODEL_MIGRATION_LIST, 'migrations', Response::MODEL_MIGRATION));
Response::setModel(new BaseList('Migrations Firebase Projects List', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', Response::MODEL_MIGRATION_FIREBASE_PROJECT));
Response::setModel(new BaseList('Specifications List', Response::MODEL_SPECIFICATION_LIST, 'specifications', Response::MODEL_SPECIFICATION));
Response::setModel(new BaseList('VCS Content List', Response::MODEL_VCS_CONTENT_LIST, 'contents', Response::MODEL_VCS_CONTENT));
// Entities
Response::setModel(new Database());
// Collection API Models
Response::setModel(new Collection());
Response::setModel(new Attribute());
Response::setModel(new AttributeList());
Response::setModel(new AttributeString());
Response::setModel(new AttributeInteger());
Response::setModel(new AttributeFloat());
Response::setModel(new AttributeBoolean());
Response::setModel(new AttributeEmail());
Response::setModel(new AttributeEnum());
Response::setModel(new AttributeIP());
Response::setModel(new AttributeURL());
Response::setModel(new AttributeDatetime());
Response::setModel(new AttributeRelationship());
Response::setModel(new AttributePoint());
Response::setModel(new AttributeLine());
Response::setModel(new AttributePolygon());
Response::setModel(new AttributeVarchar());
Response::setModel(new AttributeText());
Response::setModel(new AttributeMediumtext());
Response::setModel(new AttributeLongtext());
// Table API Models
Response::setModel(new Table());
Response::setModel(new Column());
Response::setModel(new ColumnList());
Response::setModel(new ColumnString());
Response::setModel(new ColumnInteger());
Response::setModel(new ColumnFloat());
Response::setModel(new ColumnBoolean());
Response::setModel(new ColumnEmail());
Response::setModel(new ColumnEnum());
Response::setModel(new ColumnIP());
Response::setModel(new ColumnURL());
Response::setModel(new ColumnDatetime());
Response::setModel(new ColumnRelationship());
Response::setModel(new ColumnPoint());
Response::setModel(new ColumnLine());
Response::setModel(new ColumnPolygon());
Response::setModel(new ColumnVarchar());
Response::setModel(new ColumnText());
Response::setModel(new ColumnMediumtext());
Response::setModel(new ColumnLongtext());
Response::setModel(new Index());
Response::setModel(new ColumnIndex());
Response::setModel(new Row());
Response::setModel(new ModelDocument());
Response::setModel(new Log());
Response::setModel(new User());
Response::setModel(new AlgoMd5());
Response::setModel(new AlgoSha());
Response::setModel(new AlgoPhpass());
Response::setModel(new AlgoBcrypt());
Response::setModel(new AlgoScrypt());
Response::setModel(new AlgoScryptModified());
Response::setModel(new AlgoArgon2());
Response::setModel(new Account());
Response::setModel(new Preferences());
Response::setModel(new Session());
Response::setModel(new Identity());
Response::setModel(new Token());
Response::setModel(new JWT());
Response::setModel(new Locale());
Response::setModel(new LocaleCode());
Response::setModel(new File());
Response::setModel(new Bucket());
Response::setModel(new ResourceToken());
Response::setModel(new Team());
Response::setModel(new Membership());
Response::setModel(new Site());
Response::setModel(new TemplateSite());
Response::setModel(new TemplateFramework());
Response::setModel(new Func());
Response::setModel(new TemplateFunction());
Response::setModel(new TemplateRuntime());
Response::setModel(new TemplateVariable());
Response::setModel(new Installation());
Response::setModel(new ProviderRepository());
Response::setModel(new ProviderRepositoryFramework());
Response::setModel(new ProviderRepositoryRuntime());
Response::setModel(new DetectionFramework());
Response::setModel(new DetectionRuntime());
Response::setModel(new DetectionVariable());
Response::setModel(new VcsContent());
Response::setModel(new Branch());
Response::setModel(new Runtime());
Response::setModel(new Framework());
Response::setModel(new FrameworkAdapter());
Response::setModel(new Deployment());
Response::setModel(new Execution());
Response::setModel(new Project());
Response::setModel(new Webhook());
Response::setModel(new Key());
Response::setModel(new DevKey());
Response::setModel(new MockNumber());
Response::setModel(new AuthProvider());
Response::setModel(new Platform());
Response::setModel(new Variable());
Response::setModel(new Country());
Response::setModel(new Continent());
Response::setModel(new Language());
Response::setModel(new Currency());
Response::setModel(new Phone());
Response::setModel(new HealthAntivirus());
Response::setModel(new HealthQueue());
Response::setModel(new HealthStatus());
Response::setModel(new HealthCertificate());
Response::setModel(new HealthTime());
Response::setModel(new HealthVersion());
Response::setModel(new Metric());
Response::setModel(new MetricBreakdown());
Response::setModel(new UsageDatabases());
Response::setModel(new UsageDatabase());
Response::setModel(new UsageTable());
Response::setModel(new UsageCollection());
Response::setModel(new UsageUsers());
Response::setModel(new UsageStorage());
Response::setModel(new UsageBuckets());
Response::setModel(new UsageFunctions());
Response::setModel(new UsageFunction());
Response::setModel(new UsageSites());
Response::setModel(new UsageSite());
Response::setModel(new UsageProject());
Response::setModel(new Headers());
Response::setModel(new Specification());
Response::setModel(new Rule());
Response::setModel(new TemplateSMS());
Response::setModel(new TemplateEmail());
Response::setModel(new ConsoleVariables());
Response::setModel(new MFAChallenge());
Response::setModel(new MFARecoveryCodes());
Response::setModel(new MFAType());
Response::setModel(new MFAFactors());
Response::setModel(new Provider());
Response::setModel(new Message());
Response::setModel(new Topic());
Response::setModel(new Transaction());
Response::setModel(new Subscriber());
Response::setModel(new Target());
Response::setModel(new Migration());
Response::setModel(new MigrationReport());
Response::setModel(new MigrationFirebaseProject());
// Tests (keep last)
Response::setModel(new Mock());
+13 -1
View File
@@ -23,6 +23,8 @@ use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Logger;
use Utopia\Pools\Adapter\Stack as StackPool;
use Utopia\Pools\Adapter\Swoole as SwoolePool;
use Utopia\Pools\Group;
use Utopia\Pools\Pool;
use Utopia\Queue;
@@ -285,7 +287,9 @@ $register->set('pools', function () {
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
};
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
$pool = new Pool($poolAdapter, $name, $poolSize, function () use ($type, $resource, $dsn) {
// Get Adapter
switch ($type) {
case 'database':
@@ -324,6 +328,12 @@ $register->set('pools', function () {
Config::setParam('pools-' . $key, $config);
}
$reconnectAttempts = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_ATTEMPTS', 5);
$reconnectSleep = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_SLEEP', 2);
$group->setReconnectAttempts($reconnectAttempts);
$group->setReconnectSleep($reconnectSleep);
return $group;
});
@@ -360,6 +370,8 @@ $register->set('smtp', function () {
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
$mail->SMTPAutoTLS = false;
$mail->CharSet = 'UTF-8';
$mail->Timeout = 10; /* Connection timeout */
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
$from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
$email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
+295 -8
View File
@@ -4,7 +4,7 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Key;
use Appwrite\Databases\TransactionState;
use Appwrite\Event\Audit;
use Appwrite\Event\Audit as AuditEvent;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
@@ -15,10 +15,12 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\StatsResources;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\GraphQL\Schema;
use Appwrite\Network\Cors;
use Appwrite\Network\Platform;
@@ -30,6 +32,8 @@ use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code;
@@ -127,6 +131,9 @@ App::setResource('queueForMails', function (Publisher $publisher) {
App::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
App::setResource('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
App::setResource('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
@@ -146,11 +153,14 @@ App::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
App::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
return new AuditEvent($publisher);
}, ['publisher']);
App::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
App::setResource('eventProcessor', function () {
return new EventProcessor();
}, []);
App::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
@@ -275,6 +285,7 @@ App::setResource('cors', fn (array $allowedHostnames) => new Cors(
'X-Appwrite-ID',
'X-Appwrite-Timestamp',
'X-Appwrite-Session',
'X-Appwrite-Platform', // for `$platform` injection and SDK generator
// SDK generator
'X-SDK-Version',
'X-SDK-Name',
@@ -332,6 +343,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
* overwriting the previous value.
* 7. If account API key is passed, use user of the account API key as long as user ID header matches too
*/
$authorization->setDefaultStatus(true);
@@ -408,12 +420,17 @@ App::setResource('user', function (string $mode, Document $project, Document $co
// }
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_JWT_AND_COOKIE_SET);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwt->decode($authJWT);
} catch (JWTException $error) {
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
}
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
if ($mode === APP_MODE_ADMIN) {
@@ -429,6 +446,34 @@ App::setResource('user', function (string $mode, Document $project, Document $co
}
}
}
// Account based on account API key
$accountKey = $request->getHeader('x-appwrite-key', '');
$accountKeyUserId = $request->getHeader('x-appwrite-user', '');
if (!empty($accountKeyUserId) && !empty($accountKey)) {
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
$accountKeyUser = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId));
if (!$accountKeyUser->isEmpty()) {
$key = $accountKeyUser->find(
key: 'secret',
find: $accountKey,
subject: 'keys'
);
if (!empty($key)) {
$expire = $key->getAttribute('expire');
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
throw new Exception(Exception::ACCOUNT_KEY_EXPIRED);
}
$user = $accountKeyUser;
}
}
}
$dbForProject->setMetadata('user', $user->getId());
$dbForPlatform->setMetadata('user', $user->getId());
@@ -510,7 +555,7 @@ App::setResource('authorization', function () {
return new Authorization();
}, []);
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Authorization $authorization) {
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -526,6 +571,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
@@ -547,8 +593,209 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
->setNamespace('_' . $project->getSequence());
}
/**
* This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**.
*
* Accounts can be created in many ways beyond `createAccount`
* (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.
*/
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
// Only trigger events for user creation with the database listener.
if ($document->getCollection() !== 'users') {
return;
}
$queueForEvents
->setEvent('users.[userId].create')
->setParam('userId', $document->getId())
->setPayload($response->output($document, Response::MODEL_USER));
// Trigger functions, webhooks, and realtime events
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger webhooks events only if a project has them enabled */
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
/** Trigger realtime events only for non console events */
if ($queueForEvents->getProject()->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
};
/**
* Purge function events cache when functions are created, updated or deleted.
*/
$functionsEventsCacheListener = function (string $event, Document $document, Document $project, Database $dbForProject) {
if ($document->getCollection() !== 'functions') {
return;
}
if ($project->isEmpty() || $project->getId() === 'console') {
return;
}
$hostname = $dbForProject->getAdapter()->getHostname();
$cacheKey = \sprintf(
'%s-cache-%s:%s:%s:project:%s:functions:events',
$dbForProject->getCacheName(),
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant(),
$project->getId()
);
$dbForProject->getCache()->purge($cacheKey);
};
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$value = 1;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
}
switch (true) {
case $document->getCollection() === 'teams':
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
break;
case $document->getCollection() === 'users':
$queueForStatsUsage->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case $document->getCollection() === 'sessions': // sessions
$queueForStatsUsage->addMetric(METRIC_SESSIONS, $value); //per project
break;
case $document->getCollection() === 'databases': // databases
$queueForStatsUsage->addMetric(METRIC_DATABASES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value);
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_DOCUMENTS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
break;
case $document->getCollection() === 'buckets': //buckets
$queueForStatsUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$queueForStatsUsage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$queueForStatsUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'sites':
$queueForStatsUsage
->addMetric(METRIC_SITES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$queueForStatsUsage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
break;
default:
break;
}
};
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.
$queueForEventsClone = new Event($publisher);
$queueForFunctions = new Func($publisherFunctions);
$queueForWebhooks = new Webhook($publisherWebhooks);
$queueForRealtime = new Realtime();
$database
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
$response,
$queueForEventsClone->from($queueForEvents),
$queueForFunctions->from($queueForEvents),
$queueForWebhooks->from($queueForEvents),
$queueForRealtime->from($queueForEvents)
))
->on(Database::EVENT_DOCUMENT_CREATE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
->on(Database::EVENT_DOCUMENT_UPDATE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
->on(Database::EVENT_DOCUMENT_DELETE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
;
return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project', 'authorization']);
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'queueForStatsUsage', 'authorization']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
@@ -556,6 +803,7 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authoriz
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setMetadata('host', \gethostname())
@@ -585,6 +833,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
$configure = (function (Database $database) use ($project, $dsn, $authorization) {
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
@@ -636,6 +885,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
@@ -651,6 +901,11 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
};
}, ['pools', 'cache', 'authorization']);
App::setResource('audit', function ($dbForProject) {
$adapter = new AdapterDatabase($dbForProject);
return new Audit($adapter);
}, ['dbForProject']);
App::setResource('telemetry', fn () => new NoTelemetry());
App::setResource('cache', function (Group $pools, Telemetry $telemetry) {
@@ -1006,7 +1261,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
$teamInternalId = $project->getAttribute('teamInternalId', '');
} else {
$route = $utopia->match($request);
$path = $route->getPath();
$path = !empty($route) ? $route->getPath() : $request->getURI();
if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI();
$pid = explode('/', $uri)[3];
@@ -1061,15 +1316,39 @@ App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
return '';
}, ['request', 'apiKey']);
App::setResource('apiKey', function (Request $request, Document $project): ?Key {
App::setResource('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
$key = $request->getHeader('x-appwrite-key');
if (empty($key)) {
return null;
}
return Key::decode($project, $key);
}, ['request', 'project']);
$key = Key::decode($project, $team, $user, $key);
$userHeader = $request->getHeader('x-appwrite-user');
$organizationHeader = $request->getHeader('x-appwrite-organization');
$projectHeader = $request->getHeader('x-appwrite-project');
if (!empty($key->getProjectId())) {
if (empty($projectHeader) || $projectHeader !== $key->getProjectId()) {
throw new Exception(Exception::PROJECT_ID_MISSING);
}
}
if (!empty($key->getUserId())) {
if (empty($userHeader) || $userHeader !== $key->getUserId()) {
throw new Exception(Exception::USER_ID_MISSING);
}
}
if (!empty($key->getTeamId())) {
if (empty($organizationHeader) || $organizationHeader !== $key->getTeamId()) {
throw new Exception(Exception::ORGANIZATION_ID_MISSING);
}
}
return $key;
}, ['request', 'project', 'team', 'user']);
App::setResource('executor', fn () => new Executor());
@@ -1140,3 +1419,11 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A
App::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
return new TransactionState($dbForProject, $authorization);
}, ['dbForProject', 'authorization']);
App::setResource('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
+14 -3
View File
@@ -29,6 +29,7 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
@@ -66,6 +67,7 @@ if (!function_exists('getConsoleDB')) {
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, getCache());
$database
->setDatabase(APP_DATABASE)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', '_console');
@@ -122,6 +124,7 @@ if (!function_exists('getProjectDB')) {
}
$database
->setDatabase(APP_DATABASE)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
@@ -471,9 +474,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$roles = $user->getRoles($database->getAuthorization());
$channels = $realtime->connections[$connection]['channels'];
$queries = $realtime->connections[$connection]['queries'] ?? [];
$realtime->unsubscribe($connection);
$realtime->subscribe($projectId, $connection, $roles, $channels);
$realtime->subscribe($projectId, $connection, $roles, $channels, $queries);
}
}
@@ -576,6 +580,11 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$roles = $user->getRoles($authorization);
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
try {
$queries = Realtime::convertQueries($request->getQuery('queries', []));
} catch (QueryException $e) {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
}
/**
* Channels Check
@@ -584,7 +593,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
}
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
$realtime->subscribe($project->getId(), $connection, $roles, $channels, $queries);
$realtime->connections[$connection]['authorization'] = $authorization;
@@ -594,6 +603,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
'type' => 'connected',
'data' => [
'channels' => array_keys($channels),
'queries' => $queries,
'user' => $user
]
]));
@@ -728,7 +738,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
// Preserve authorization before subscribe overwrites the connection array
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
$queries = $realtime->connections[$connection]['queries'];
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels, $queries);
// Restore authorization after subscribe
if ($authorization !== null) {
+5 -1
View File
@@ -11,6 +11,7 @@ $httpsPort = $this->getParam('httpsPort', '');
$version = $this->getParam('version', '');
$organization = $this->getParam('organization', '');
$image = $this->getParam('image', '');
$enableAssistant = $this->getParam('enableAssistant', false);
?>services:
traefik:
image: traefik:2.11
@@ -164,6 +165,7 @@ $image = $this->getParam('image', '');
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_INTROSPECTION
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
@@ -179,7 +181,7 @@ $image = $this->getParam('image', '');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:7.4.7
image: <?php echo $organization; ?>/console:7.5.7
restart: unless-stopped
networks:
- appwrite
@@ -848,6 +850,7 @@ $image = $this->getParam('image', '');
- _APP_DB_USER
- _APP_DB_PASS
<?php if ($enableAssistant): ?>
appwrite-assistant:
image: appwrite/assistant:0.8.4
container_name: appwrite-assistant
@@ -857,6 +860,7 @@ $image = $this->getParam('image', '');
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
<?php endif; ?>
appwrite-browser:
image: appwrite/browser:0.3.2
+32 -5
View File
@@ -14,6 +14,7 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
@@ -21,6 +22,8 @@ use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit as UtopiaAudit;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -62,6 +65,7 @@ Server::setResource('dbForPlatform', function (Cache $cache, Registry $register,
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class)
@@ -115,6 +119,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
@@ -176,6 +181,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
@@ -195,6 +201,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
@@ -321,6 +328,10 @@ Server::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
Server::setResource('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
Server::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
@@ -466,6 +477,27 @@ Server::setResource('logError', function (Registry $register, Document $project)
Server::setResource('executor', fn () => new Executor());
Server::setResource('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
if ($project->isEmpty() || $project->getId() === 'console') {
$adapter = new AdapterDatabase($dbForPlatform);
return new UtopiaAudit($adapter);
}
$dbForProject = $getProjectDB($project);
$adapter = new AdapterDatabase($dbForProject);
return new UtopiaAudit($adapter);
};
}, ['dbForPlatform', 'getProjectDB']);
Server::setResource('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
$pools = $register->get('pools');
$platform = new Appwrite();
$args = $platform->getEnv('argv');
@@ -546,9 +578,4 @@ $worker
Console::error('[Error] Line: ' . $error->getLine());
});
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->start();
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php doctor $@
exec php /usr/src/code/app/cli.php doctor "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php install $@
exec php /usr/src/code/app/cli.php install "$@"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php interval "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php maintenance $@
exec php /usr/src/code/app/cli.php maintenance "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php migrate $@
exec php /usr/src/code/app/cli.php migrate "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=failed $@
exec php /usr/src/code/app/cli.php queue-count --type=failed "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=processing $@
exec php /usr/src/code/app/cli.php queue-count --type=processing "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=success $@
exec php /usr/src/code/app/cli.php queue-count --type=success "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-retry $@
exec php /usr/src/code/app/cli.php queue-retry "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/realtime.php $@
exec php /usr/src/code/app/realtime.php "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-executions $@
exec php /usr/src/code/app/cli.php schedule-executions "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-functions $@
exec php /usr/src/code/app/cli.php schedule-functions "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-messages $@
exec php /usr/src/code/app/cli.php schedule-messages "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php screenshot $@
exec php /usr/src/code/app/cli.php screenshot "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php sdks $@
exec php /usr/src/code/app/cli.php sdks "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php specs $@
exec php /usr/src/code/app/cli.php specs "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php ssl $@
exec php /usr/src/code/app/cli.php ssl "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php stats-resources $@
exec php /usr/src/code/app/cli.php stats-resources "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
exec /usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php upgrade $@
exec php /usr/src/code/app/cli.php upgrade "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php vars $@
exec php /usr/src/code/app/cli.php vars "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php audits $@
exec php /usr/src/code/app/worker.php audits "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php builds $@
exec php /usr/src/code/app/worker.php builds "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php certificates $@
exec php /usr/src/code/app/worker.php certificates "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php databases $@
exec php /usr/src/code/app/worker.php databases "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php deletes $@
exec php /usr/src/code/app/worker.php deletes "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php functions $@
exec php /usr/src/code/app/worker.php functions "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php mails $@
exec php /usr/src/code/app/worker.php mails "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php messaging $@
exec php /usr/src/code/app/worker.php messaging "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php migrations $@
exec php /usr/src/code/app/worker.php migrations "$@"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php screenshots "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php stats-resources $@
exec php /usr/src/code/app/worker.php stats-resources "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php stats-usage $@
exec php /usr/src/code/app/worker.php stats-usage "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php webhooks $@
exec php /usr/src/code/app/worker.php webhooks "$@"
+11 -11
View File
@@ -38,7 +38,7 @@
"ext-yaml": "*",
"ext-dom": "*",
"ext-redis": "*",
"ext-swoole": "*",
"ext-swoole": "6.*",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-zlib": "*",
@@ -47,32 +47,32 @@
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "1.*",
"utopia-php/audit": "2.*",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "1.*.*",
"utopia-php/config": "1.*",
"utopia-php/database": "4.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.9.*",
"utopia-php/domains": "0.11.*",
"utopia-php/emails": "0.6.*",
"utopia-php/dns": "1.4.*",
"utopia-php/dns": "1.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.4.*",
"utopia-php/fetch": "0.5.*",
"utopia-php/image": "0.8.*",
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.3.*",
"utopia-php/migration": "1.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
"utopia-php/pools": "1.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.11.*",
"utopia-php/queue": "0.15.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
"utopia-php/swoole": "1.*",
"utopia-php/system": "0.9.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/vcs": "0.13.*",
@@ -91,7 +91,7 @@
"ext-fileinfo": "*",
"appwrite/sdk-generator": "*",
"phpunit/phpunit": "9.*",
"swoole/ide-helper": "5.1.2",
"swoole/ide-helper": "6.*",
"phpstan/phpstan": "1.8.*",
"textalk/websocket": "1.5.*",
"laravel/pint": "1.*",
Generated
+339 -230
View File
File diff suppressed because it is too large Load Diff
+139 -27
View File
@@ -98,6 +98,7 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
# - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
depends_on:
- mariadb
- redis
@@ -111,7 +112,9 @@ services:
- _APP_ENV
- _APP_EDITION
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOCALE
- _APP_COMPRESSION_ENABLED
- _APP_COMPRESSION_MIN_SIZE_BYTES
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
@@ -200,6 +203,7 @@ services:
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_INTROSPECTION
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
@@ -230,7 +234,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:7.4.11
image: appwrite/console:7.5.7
restart: unless-stopped
networks:
- appwrite
@@ -298,6 +302,7 @@ services:
- _APP_LOGGING_CONFIG
- _APP_LOGGING_CONFIG_REALTIME
- _APP_DATABASE_SHARED_TABLES
- _APP_POOL_ADAPTER=swoole
appwrite-worker-audits:
entrypoint: worker-audits
@@ -315,6 +320,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -346,6 +352,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_DB_HOST
@@ -383,6 +390,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -440,6 +448,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -466,16 +475,15 @@ services:
- appwrite-functions:/storage/functions:rw
- appwrite-sites:/storage/sites:rw
- appwrite-builds:/storage/builds:rw
- appwrite-uploads:/storage/uploads:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_BROWSER_HOST
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
@@ -529,6 +537,66 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
appwrite-worker-screenshots:
entrypoint: worker-screenshots
<<: *x-logging
container_name: appwrite-worker-screenshots
image: appwrite-dev
networks:
- appwrite
volumes:
- appwrite-uploads:/storage/uploads:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
# Specific
- _APP_BROWSER_HOST
# Basic
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOGGING_CONFIG
# Database
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DATABASE_SHARED_TABLES
# Storage
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_S3_ENDPOINT
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
extra_hosts:
- "host.docker.internal:host-gateway"
appwrite-worker-certificates:
entrypoint: worker-certificates
<<: *x-logging
@@ -547,6 +615,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -555,6 +624,7 @@ services:
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -579,12 +649,16 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- openruntimes-executor
redis:
condition: service_started
mariadb:
condition: service_started
openruntimes-executor:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
@@ -628,6 +702,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
@@ -662,6 +737,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -719,6 +795,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -753,10 +830,12 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -785,6 +864,45 @@ services:
- _APP_MAINTENANCE_START_TIME
- _APP_DATABASE_SHARED_TABLES
appwrite-task-interval:
entrypoint: interval
<<: *x-logging
container_name: appwrite-task-interval
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DATABASE_SHARED_TABLES
- _APP_INTERVAL_DOMAIN_VERIFICATION
- _APP_INTERVAL_CLEANUP_STALE_EXECUTIONS
appwrite-task-stats-resources:
container_name: appwrite-task-stats-resources
entrypoint: stats-resources
@@ -801,6 +919,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -832,6 +951,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -863,6 +983,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -894,6 +1015,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -922,6 +1044,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -949,6 +1072,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -1027,27 +1151,12 @@ services:
- OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
openruntimes-proxy:
container_name: openruntimes-proxy
hostname: proxy
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/proxy:0.5.5
networks:
- appwrite
- runtimes
environment:
- OPR_PROXY_WORKER_PER_CORE=$_APP_WORKER_PER_CORE
- OPR_PROXY_ENV=$_APP_ENV
- OPR_PROXY_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_PROXY_SECRET=$_APP_EXECUTOR_SECRET
- OPR_PROXY_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
- OPR_PROXY_ALGORITHM=random
- OPR_PROXY_EXECUTORS=exc1
- OPR_PROXY_HEALTHCHECK_INTERVAL=10000
- OPR_PROXY_MAX_TIMEOUT=600
- OPR_PROXY_HEALTHCHECK=enabled
healthcheck:
test: ["CMD-SHELL", "curl -fsS -H \"Authorization: Bearer $$OPR_EXECUTOR_SECRET\" http://localhost/v1/health >/dev/null"]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
@@ -1131,6 +1240,9 @@ services:
- "traefik.http.routers.appwrite_maildev_https.rule=Host(`mail.localhost`)"
- "traefik.http.routers.appwrite_maildev_https.service=appwrite_maildev"
- "traefik.http.routers.appwrite_maildev_https.tls=true"
environment:
- MAILDEV_INCOMING_USER=${_APP_SMTP_USERNAME}
- MAILDEV_INCOMING_PASS=${_APP_SMTP_PASSWORD}
request-catcher-webhook: # used mainly for dev tests (mock HTTP webhook)
image: appwrite/requestcatcher:1.0.0
@@ -0,0 +1 @@
appwrite health get-queue-audits
@@ -0,0 +1,3 @@
appwrite projects update-labels \
--project-id <PROJECT_ID> \
--labels one two three

Some files were not shown because too many files have changed in this diff Show More