Compare commits

..
Author SHA1 Message Date
fogelito 4d670bb47d Remove use 2025-07-20 12:06:23 +03:00
fogelito a9e53a46a6 formatting 2025-07-20 12:04:55 +03:00
fogelito e2797ab9d4 Disable index array 2025-07-20 11:57:43 +03:00
Steven NguyenandGitHub 8b90acd8c6 Merge pull request #10164 from appwrite/improve-scheme-error
chore: improve invalid scheme error in origin check
2025-07-19 07:16:20 -07:00
Chirag Aggarwal 7fecfc73ca fix: formatting 2025-07-19 10:39:39 +05:30
Chirag Aggarwal c55f039f13 chore: add unit test 2025-07-18 22:23:33 +05:30
Chirag Aggarwal d613237fb4 chore: improve invalid scheme error in origin check 2025-07-18 22:08:43 +05:30
Steven NguyenandGitHub 5742dad1e4 Merge pull request #10161 from appwrite/update-dart-flutter
chore: update dart 16.1.0, flutter 17.0.2 and cli 8.2.2
2025-07-18 08:49:34 -07:00
Chirag Aggarwal ea0654cf5e improve naming 2025-07-18 17:16:01 +05:30
Chirag Aggarwal 99ea9c5d80 chore: update cli to 8.2.2 2025-07-18 17:10:20 +05:30
Chirag Aggarwal 712425c383 fix: version 2025-07-18 15:59:09 +05:30
Chirag Aggarwal 0cc443b47c fix: branch name 2025-07-18 15:56:12 +05:30
Chirag Aggarwal c729a84d51 chore: update dart to 16.1.0 and flutter to 17.0.2 2025-07-18 15:55:11 +05:30
Jake BarnbyandGitHub 33435890dd Merge pull request #10158 from appwrite/fix-redirect-specs
Handle redirect validator in specs + GraphQL type mapper
2025-07-18 12:19:22 +12:00
Jake Barnby 0a5fbb7bc7 Handle redirect validator in specs + GraphQL type mapper 2025-07-18 11:59:32 +12:00
Matej BačoandGitHub 1e61091834 Merge pull request #10157 from appwrite/fix-build-usage-specification
fix: build usage specification
2025-07-17 15:33:38 +02:00
loks0n 94decb26e6 fix: build usage specification 2025-07-17 14:05:05 +01:00
Matej BačoandGitHub a3f9707938 Merge pull request #10155 from appwrite/update-cli-8.2.1
Update cli 8.2.1
2025-07-17 11:55:14 +02:00
Chirag Aggarwal 0a82b6d30d chore: update CLI to 8.2.1 2025-07-17 11:44:30 +05:30
Damodar LohaniandGitHub 0f38bb689f Merge pull request #10138 from appwrite/update-react-native-0.10.1
chore: update react native to 0.10.1 and dotnet to 0.14.0
2025-07-15 10:39:30 +05:45
Steven NguyenandGitHub 00889426de Merge pull request #10150 from appwrite/fix-apple-oauth
fix: prevent injected $user from being shadowed
2025-07-14 21:29:16 -07:00
Steven Nguyen 477682e062 fix: prevent injected $user from being shadowed 2025-07-14 16:05:58 -07:00
Chirag Aggarwal 054b6a995a update dotnet to 0.14.0 2025-07-14 08:54:37 +05:30
Chirag Aggarwal a2ca08023d chore: update composer 2025-07-12 12:18:11 +05:30
Chirag Aggarwal 8a2e0d4970 chore: update react native to 0.10.1 and changelog 2025-07-12 12:04:51 +05:30
Matej BačoandGitHub 51d07832f7 Merge pull request #10136 from appwrite/update-cli-8.2.0
chore: update cli to 8.2.0
2025-07-11 16:39:13 +02:00
Matej BačoandGitHub 8c57d28f8f Merge pull request #10127 from appwrite/chore-speed-up-tests
Chore: speed up tests
2025-07-11 16:21:40 +02:00
Matej Bačo 0bdb64fc77 Fix migration tests 2025-07-11 15:54:53 +02:00
Chirag Aggarwal 32cb5e0a79 chore: update cli to 8.2.0 2025-07-11 16:31:50 +05:30
Matej BačoandGitHub e24671f6f4 Merge pull request #10135 from appwrite/fix-staging-specs
Fix: specs with new env vars
2025-07-11 12:08:21 +02:00
Matej Bačo 9bab764378 Fix specs with new env vars 2025-07-11 11:15:55 +02:00
Steven NguyenandGitHub fd35043dd7 Merge pull request #10119 from appwrite/fix-mobile-oauth2-redirect
fix: update OAuth2 redirect URLs
2025-07-10 16:46:58 -07:00
Steven Nguyen 100aea337f fix: update OAuth2 redirect URLs
1. handle non-standard ports if the request came in on a non-standard port
2. use the _APP_CONSOLE_DOMAIN env var to handle cases where the Console domain may not be the endpoint domain such as on Appwrite Cloud
3. use the _APP_OPTIONS_FORCE_HTTPS env var to determine the protocol to be consistent with the rest of the codebase
2025-07-10 15:51:13 -07:00
Steven NguyenandGitHub 435a319fb6 Merge pull request #10130 from appwrite/fix-oauth2-redirect-validation
fix: success validation in oauth2 redirect
2025-07-10 09:06:43 -07:00
Matej Bačo 18492ab917 Revert unwanted change 2025-07-10 16:16:06 +02:00
Matej Bačo 89d54f847f Speed up build cancellation 2025-07-10 15:52:29 +02:00
Matej Bačo 6dc6ff6ba0 Fix formatting 2025-07-10 14:44:44 +02:00
Matej Bačo 581235f972 Fix more tests 2025-07-10 14:43:56 +02:00
Matej Bačo f02ba4c976 Fix function tests 2025-07-10 14:25:53 +02:00
Matej Bačo eaf9b8b228 Migrate remaining tests to node 2025-07-10 12:55:45 +02:00
Steven Nguyen 11332ed1f9 chore: fix deprecation warning from passing null into json_decode 2025-07-09 22:10:17 -07:00
Steven Nguyen 990e1b91bb fix: omit : and @ if user and pass are empty
So, unparsing should not end up with https://:@appwrite.io just because user and pass are empty strings.
2025-07-09 22:09:18 -07:00
Steven Nguyen f202685450 fix: handle parsing URL that is only a scheme
In react native, a redirect URL may only be a url scheme such as appwrite-callback-myproject://. Since parse_url() fails on this type of URL, we need to add this workaround.
2025-07-09 22:01:38 -07:00
Steven NguyenandGitHub 9b90d8030e Merge pull request #10129 from appwrite/dat-579
Changed the default permission param of upsert document
2025-07-09 20:44:36 -07:00
Steven Nguyen 53086fe975 fix: success validation in oauth2 redirect
We switched to using the Redirect class for validating redirect URLs to cover additional cases like react native expo scheme, but we missed this validation.
2025-07-09 15:11:30 -07:00
ArnabChatterjee20k 1d9aac5a49 updated upsert document test with api key 2025-07-09 23:57:39 +05:30
ArnabChatterjee20k ca61b528b9 reverted the api signature and started handling it internally 2025-07-09 23:49:56 +05:30
ArnabChatterjee20k 50109b833a removed tests for the upsert without permissions for passing the tests 2025-07-09 21:49:07 +05:30
Matej Bačo 5ace34790d Migrate many tests to JS 2025-07-09 16:58:55 +02:00
Matej Bačo 1a3f8bbd48 quality fixes 2025-07-09 14:29:36 +02:00
Matej BačoandGitHub 41fc925103 Merge branch '1.7.x' into chore-speed-up-tests 2025-07-09 13:47:33 +02:00
Matej BačoandGitHub a756e533e8 Merge pull request #10128 from appwrite/reduce-delete-batch-size
Reduce delete batch size
2025-07-09 13:46:03 +02:00
Matej BačoandGitHub 449b9295fd Update src/Appwrite/Platform/Workers/Deletes.php 2025-07-09 13:42:18 +02:00
fogelito 3a298b7a21 Change to 500 2025-07-09 14:32:47 +03:00
ArnabChatterjee20k 13bd2341c0 empty commit to trigger tests 2025-07-09 17:02:44 +05:30
ArnabChatterjee20k c36720e3b0 updated param of permissiosns in upsert and updated tests for upsert without permissions 2025-07-09 16:37:41 +05:30
fogelito 9cc61d89f0 $deleteBatchSize 2025-07-09 14:00:08 +03:00
Matej BačoandGitHub cc1129a7b9 Merge pull request #9952 from appwrite/fix-build-activation-race-condition
Fix build activation race condition
2025-07-09 11:59:40 +02:00
Matej Bačo e42275e0b7 Minify tests 2025-07-09 11:26:05 +02:00
Matej Bačo 26496e68c4 Attempt to simplify code 2025-07-08 14:51:28 +02:00
Khushboo VermaandGitHub f7cbe5b961 Merge branch '1.7.x' into fix-build-activation-race-condition 2025-07-08 17:53:12 +05:30
Khushboo Verma bd63f0cf3d Remove empty check 2025-07-08 17:46:54 +05:30
Khushboo Verma bc9caeaae0 Use ruleId 2025-07-08 17:45:45 +05:30
Khushboo Verma a5a1860ba6 Use createdAt instead of buildStartTime 2025-07-08 17:42:23 +05:30
Khushboo Verma 9d7e218e99 Save result of updateDocument 2025-07-08 17:12:35 +05:30
Matej BačoandGitHub 6d321f6988 Merge pull request #10120 from appwrite/fix-file-tokens
Fix file tokens not working on file-security
2025-07-08 13:41:10 +02:00
Khushboo Verma 61a27bfe55 Simplify auto-activation logic and just update attributes 2025-07-08 16:30:43 +05:30
Darshan 7ca951ed81 remove: {} 2025-07-08 15:43:51 +05:30
Darshan 020814e672 update: inline guest headers. 2025-07-08 15:43:28 +05:30
Darshan 7ea896301f update: test to include preview, view and download with file-security. 2025-07-08 15:42:38 +05:30
Darshan 97d5f0f030 add: e2e. 2025-07-08 15:34:52 +05:30
Darshan 14e52058c7 fix: file tokens not working on file-security. 2025-07-08 13:46:53 +05:30
Khushboo Verma 067b53482f Fix bug 2025-07-08 01:02:55 +05:30
Khushboo Verma 1a286ad3f4 Revert replica changes 2025-07-08 00:53:10 +05:30
Khushboo Verma 80c4982482 Update only specific attribute 2025-07-08 00:51:35 +05:30
Khushboo Verma a23c99ad7a Merge branch 'fix-build-activation-race-condition' of github.com:appwrite/appwrite into fix-build-activation-race-condition 2025-07-07 12:37:06 +05:30
Khushboo Verma ea238bc5ec Fix merge conflicts 2025-07-07 12:36:42 +05:30
Damodar LohaniandGitHub edd7a4ac42 Merge pull request #10088 from appwrite/update-nodejs-17.1.0
Update nodejs 17.1.0
2025-07-07 11:14:25 +05:45
Chirag AggarwalandGitHub f678add130 Merge branch '1.7.x' into update-nodejs-17.1.0 2025-07-07 10:39:56 +05:30
Steven NguyenandGitHub 543b872f60 Merge pull request #10107 from appwrite/fix-origin-validation-extensions
fix: origin validation for web extensions
2025-07-06 14:53:56 -07:00
Chirag Aggarwal 5fffae39d4 chore: update naming and add tests 2025-07-05 17:59:01 +05:30
Chirag Aggarwal 9dcb80649f fix: extension for safari 2025-07-05 10:18:40 +05:30
Chirag Aggarwal 18d30c48d5 fix: add missing check for hostname 2025-07-05 10:15:18 +05:30
Chirag Aggarwal 0fb55f6312 fix: origin validation for web extensions 2025-07-05 07:59:52 +05:30
Matej BačoandGitHub 1cf9963bb6 Merge pull request #10104 from appwrite/chore-fix-site-template-test
Fix site template test
2025-07-04 15:17:09 +02:00
Khushboo Verma 2545977d47 Fix site template test 2025-07-04 17:46:21 +05:30
Torsten DittmannandGitHub dbadfa7bf8 Merge pull request #9650 from appwrite/feat-exp-scheme
feat: exp scheme
2025-07-04 14:09:54 +02:00
Chirag Aggarwal 37c7411405 Trigger Build 2025-07-04 16:51:30 +05:30
Chirag Aggarwal bc8b10a69e fix: origin tests 2025-07-04 16:46:14 +05:30
Chirag Aggarwal 7bbf58d0dd update cli 2025-07-04 15:31:52 +05:30
Chirag AggarwalandGitHub df9ef1ace9 Merge branch '1.7.x' into feat-exp-scheme 2025-07-04 15:22:44 +05:30
Matej BačoandGitHub 32903d9a81 Merge pull request #9999 from appwrite/chore-update-env-var-used-for-domain
Use _APP_CONSOLE_DOMAIN, if not found, then use _APP_DOMAIN
2025-07-04 09:41:42 +02:00
Khushboo VermaandGitHub 131cdefac5 Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-07-04 10:40:38 +05:30
Chirag AggarwalandGitHub 231225ee10 Merge branch '1.7.x' into update-nodejs-17.1.0 2025-07-04 09:37:51 +05:30
Jake BarnbyandGitHub d205ac4579 Merge pull request #10099 from appwrite/feat-redis-publisher-scheduler
Add redis publisher to schedule base if available
2025-07-03 18:49:25 +00:00
Jake Barnby 5bb998b02d Add API + CLI stubs 2025-07-03 14:24:40 -04:00
Jake Barnby 7d37f5b066 Lint 2025-07-03 14:18:19 -04:00
Jake Barnby 2a73dddbdb Fix health 2025-07-03 14:09:08 -04:00
Jake Barnby f224c09c10 Use correct publisher in schedule tasks 2025-07-03 14:08:54 -04:00
Jake Barnby 6725b31b65 Add redis publisher to schedule base if available 2025-07-03 13:55:13 -04:00
Chirag AggarwalandGitHub e721a9f11c Merge branch '1.7.x' into feat-exp-scheme 2025-07-03 22:40:46 +05:30
Chirag Aggarwal 72f08e281b chore: make check consistent 2025-07-03 22:39:12 +05:30
Chirag Aggarwal 431a26e151 chore: removing loggin 2025-07-03 22:37:31 +05:30
Chirag Aggarwal 172ba84ec8 chore: add logging 2025-07-03 22:25:46 +05:30
Torsten DittmannandGitHub 7c0216c592 Merge pull request #10098 from appwrite/fix/jwt-auth-db-admin-mode
fix: JWT authentication database selection for admin mode
2025-07-03 18:29:54 +02:00
Chirag Aggarwal b6f11144b8 chore: popular schemes 2025-07-03 21:39:23 +05:30
Ariel WeinbergerandGitHub 199b9f7e6d fix nitpick 2025-07-03 11:06:36 -05:00
Ariel WeinbergerandGitHub d9486f38c4 fix: jwt auth database selection for admin mode
Use dbForPlatform for JWT user lookup in admin mode
2025-07-03 10:54:51 -05:00
Chirag Aggarwal 4747700eae chore: add logging 2025-07-03 21:17:43 +05:30
Chirag Aggarwal 58c2f55d1c fix: revert to main 2025-07-03 18:51:50 +05:30
Chirag Aggarwal 2cf7246c7a fix: changelog 2025-07-03 18:15:50 +05:30
Chirag Aggarwal dd9096fd2b fix: lint 2025-07-03 18:04:28 +05:30
Chirag Aggarwal 60158aec0c chore: update cli to 8.1.1 as well 2025-07-03 18:02:40 +05:30
Chirag Aggarwal bb9a271fa8 chore: add scheme validation 2025-07-03 16:39:41 +05:30
Chirag AggarwalandGitHub ef2dfb6cca Merge branch '1.7.x' into feat-exp-scheme 2025-07-03 16:37:17 +05:30
Chirag Aggarwal f62c3135df add null check for platform 2025-07-03 16:26:56 +05:30
Khushboo VermaandGitHub 642d046d65 Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-07-03 00:38:33 +05:30
Jake BarnbyandGitHub d38b25e0b1 Merge pull request #10090 from appwrite/csv-fix
Fix: path and type
2025-07-02 19:04:00 +00:00
Khushboo Verma 9064fcc24e Merge branch 'chore-update-env-var-used-for-domain' of github.com:appwrite/appwrite into chore-update-env-var-used-for-domain 2025-07-03 00:08:58 +05:30
Khushboo Verma 0f37a0e0ef Fix tests 2025-07-03 00:08:29 +05:30
Khushboo Verma 253195698e Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-07-03 00:02:51 +05:30
Jake Barnby 55dc965dd6 Update lib 2025-07-02 14:32:05 -04:00
Jake Barnby 2ce3118ba9 Remove path update 2025-07-02 13:36:46 -04:00
Jake Barnby ce8ae2294a Update lock 2025-07-02 12:58:37 -04:00
Jake Barnby b1724e9c9d Update lock 2025-07-02 10:40:35 -04:00
Darshan ed2d12f896 update: full path on appwrite. 2025-07-02 19:55:42 +05:30
Darshan 6f5d3d53c0 bump: for tests. 2025-07-02 19:51:36 +05:30
Darshan 6d792fcb6c revert: lock. 2025-07-02 19:20:30 +05:30
Darshan fc1a9a4e16 fix: path and revert deps. 2025-07-02 19:13:13 +05:30
Darshan 0e50fb7ec5 add: migrations lib logging for errors. 2025-07-02 17:04:26 +05:30
Darshan d34635becb add: logging for errors. 2025-07-02 15:13:42 +05:30
Darshan eb1d4c68ea add: logging. 2025-07-02 14:54:25 +05:30
Darshan beec2e780d Merge branch '1.7.x' into csv-fix 2025-07-02 14:35:38 +05:30
Darshan aa9a82d8ce fix: document attribute type. 2025-07-02 14:32:16 +05:30
Chirag Aggarwal 421510f847 chore: update nodejs to 17.1.0 2025-07-02 11:01:25 +05:30
Khushboo VermaandGitHub 2da811956c Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-07-01 12:22:36 +05:30
Khushboo VermaandGitHub 8ef24cb2a9 Merge branch '1.7.x' into fix-build-activation-race-condition 2025-07-01 12:22:17 +05:30
Khushboo Verma 58c1e2d6e2 Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-07-01 02:29:08 +05:30
Khushboo Verma ddb5c52e7c Resolve merge conflicts 2025-07-01 02:25:38 +05:30
Chirag Aggarwal 2d00052db6 fix: general test 2025-06-30 20:50:29 +05:30
Chirag Aggarwal db56361ad9 fix: add back empty checks 2025-06-30 19:48:05 +05:30
Chirag Aggarwal 843d4d9ea0 chore: use origin validator 2025-06-30 19:29:42 +05:30
Chirag Aggarwal 8a2ccd32b4 fix: remove redundant check 2025-06-30 19:09:15 +05:30
Chirag Aggarwal 39db3a3066 fix: order 2025-06-30 19:07:49 +05:30
Chirag Aggarwal 9242ef85eb fix: order 2025-06-30 19:03:12 +05:30
Chirag Aggarwal 332c82e61e fix: lint 2025-06-30 19:00:54 +05:30
Chirag Aggarwal bfee98ca87 Merge branch '1.7.x' into feat-exp-scheme 2025-06-30 18:38:25 +05:30
Chirag AggarwalandGitHub 8027251224 Merge branch '1.7.x' into feat-exp-scheme 2025-06-27 09:21:27 +05:30
Chirag Aggarwal fffdab23f9 chore: update scheme check 2025-06-27 08:29:13 +05:30
Chirag Aggarwal 1555abcbd6 chore: use lowercase for check 2025-06-26 19:59:58 +05:30
Chirag Aggarwal f8e74d073a chore: fix scheme check, remove logging 2025-06-26 19:48:57 +05:30
Chirag Aggarwal be40f1cbe3 chore: remove check for scheme 2025-06-26 19:44:41 +05:30
Chirag Aggarwal 043cead66b chore: fix logging 2025-06-26 19:32:15 +05:30
Chirag Aggarwal 7e378a3a1e chore: more logging 2025-06-26 19:10:35 +05:30
Chirag Aggarwal e13089b1ce chore: more logging 2025-06-26 19:09:14 +05:30
Chirag Aggarwal 9861f60666 chore: add logging 2025-06-26 18:55:51 +05:30
Chirag Aggarwal 06ce606ac7 Merge branch '1.7.x' into feat-exp-scheme 2025-06-26 18:38:10 +05:30
Chirag Aggarwal 1a9664a679 chore: fix benchmark 2025-06-25 11:43:18 +05:30
Chirag Aggarwal 65b5af1b00 chore: fix tests 2025-06-25 11:27:23 +05:30
Chirag Aggarwal 6dda6e1fda chore: remove console log 2025-06-25 11:25:39 +05:30
Chirag Aggarwal c331a3caae chore: minor fixes 2025-06-25 11:00:07 +05:30
Chirag Aggarwal dbc58655b2 Merge branch '1.7.x' into feat-exp-scheme 2025-06-25 07:53:28 +05:30
Khushboo Verma e65ed58ca4 Merge branch 'chore-update-env-var-used-for-domain' of github.com:appwrite/appwrite into chore-update-env-var-used-for-domain 2025-06-18 22:17:11 +05:30
Khushboo Verma 095159d89b Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-06-18 22:15:19 +05:30
Christy JacobandGitHub 509392c48b Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-06-18 13:44:04 +04:00
Khushboo Verma 520006864d Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-06-18 11:07:25 +05:30
Khushboo Verma 5a3e44a36d Replace == with === 2025-06-18 11:06:35 +05:30
Khushboo Verma 22cda269b1 Update console 2025-06-17 18:37:02 +05:30
Khushboo Verma e8e7ee01b4 Merge branch 'fix-github-check-urls' into chore-update-env-var-used-for-domain 2025-06-17 14:01:50 +05:30
Khushboo Verma b7ffd2ca94 Revert some changes 2025-06-17 13:57:21 +05:30
Khushboo Verma 9356fb4cb4 Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-06-17 12:16:37 +05:30
Khushboo Verma 41bc8272ff Add env var to docker-compose 2025-06-17 12:15:52 +05:30
Khushboo Verma ad60ff9972 Update url in general.php 2025-06-16 19:16:20 +05:30
Khushboo Verma 8c8abfdece Add var_dumps to debug 2025-06-16 18:47:50 +05:30
Khushboo Verma 5e345c0388 Revert some changes 2025-06-16 17:29:01 +05:30
Khushboo Verma 362618c5af Merge branch 'fix-github-check-urls' into chore-update-env-var-used-for-domain 2025-06-16 17:19:55 +05:30
Khushboo Verma 3ad5a2652e Update console version 2025-06-13 19:14:00 +05:30
Khushboo Verma 88d9bdd395 Merge branch 'fix-build-activation-race-condition' of github.com:appwrite/appwrite into fix-build-activation-race-condition 2025-06-13 19:05:12 +05:30
Khushboo Verma 995482ee20 Update console version 2025-06-13 19:04:46 +05:30
Khushboo Verma d2abdea6cf Resolve merge conflicts 2025-06-13 19:04:17 +05:30
Khushboo Verma cee6420fa2 Use env vars instead of request 2025-06-13 15:50:53 +05:30
Khushboo Verma 8bac836483 Merge branch 'fix-github-check-urls' into chore-update-env-var-used-for-domain 2025-06-13 15:18:31 +05:30
Khushboo Verma 01aa9fbb48 Merge branch 'chore-update-env-var-used-for-domain' of github.com:appwrite/appwrite into chore-update-env-var-used-for-domain 2025-06-13 15:18:17 +05:30
Khushboo VermaandGitHub d7c3100fe9 Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-06-13 15:18:02 +05:30
Khushboo Verma 43f9254302 Merge branch '1.7.x' into chore-update-env-var-used-for-domain 2025-06-12 20:32:02 +05:30
Khushboo Verma 710b8bda56 Use _APP_CONSOLE_DOMAIN, if not found, then use _APP_DOMAIN 2025-06-12 17:27:21 +05:30
Khushboo VermaandGitHub dde7570072 Merge branch '1.7.x' into fix-build-activation-race-condition 2025-06-10 16:21:51 +05:30
Khushboo Verma e44d1b6600 Merge branch '1.7.x' into fix-build-activation-race-condition 2025-06-03 14:23:51 +05:30
Khushboo Verma 2088792d09 Fix build activation race condition 2025-06-03 14:23:17 +05:30
Khushboo Verma 7a465dc8f8 Don't auto-activate deployment which started earlier & ended later than active deployment 2025-06-02 17:53:35 +05:30
Luke B. SilverandGitHub d82812cc04 Merge branch '1.6.x' into feat-exp-scheme 2025-04-15 10:11:31 +01:00
loks0n 2427f950b5 fix: realtime 2025-04-14 14:34:22 +01:00
loks0n e551e4ad5b chore: fmt 2025-04-14 14:23:01 +01:00
loks0n 67a313cef3 fix: tests 2025-04-14 13:50:48 +01:00
loks0n 3c2b88dc86 chore: remove unused 2025-04-14 13:02:02 +01:00
loks0n 944624f13a chore: fmt 2025-04-14 12:59:39 +01:00
loks0n 4ac2571135 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-exp-scheme 2025-04-14 12:58:25 +01:00
loks0n 0a4c430ae7 feat: exp scheme 2025-04-14 12:56:42 +01:00
107 changed files with 1592 additions and 1076 deletions
+4 -2
View File
@@ -22,6 +22,7 @@ _APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_CONSOLE_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=test.localhost
@@ -84,8 +85,9 @@ _APP_COMPUTE_MAINTENANCE_INTERVAL=600
_APP_COMPUTE_RUNTIMES_NETWORK=runtimes
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://exc1/v1
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.32
_APP_BROWSER_HOST=http://appwrite-browser:3000/v1
_APP_FUNCTIONS_RUNTIMES=node-22
_APP_SITES_RUNTIMES=static-1,node-22
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_START_TIME=12:00
_APP_MAINTENANCE_RETENTION_CACHE=2592000
+85 -4
View File
@@ -178,6 +178,7 @@ jobs:
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's|^_APP_BROWSER_HOST=.*|_APP_BROWSER_HOST=http://invalid-browser/v1|' .env
docker compose up -d
sleep 30
@@ -198,7 +199,7 @@ jobs:
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
e2e_shared_mode_test:
name: E2E Shared Mode Service Test
@@ -277,14 +278,12 @@ jobs:
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
runs-on: ubuntu-latest
needs: setup
strategy:
fail-fast: false
steps:
- name: checkout
uses: actions/checkout@v4
@@ -360,3 +359,85 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
e2e_screenshots_keys:
name: E2E Service Test (Site Screenshots)
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
docker compose up -d
sleep 30
- name: Run Site tests with browser connected in dedicated table mode
run: |
echo "Keeping original value of _APP_BROWSER_HOST"
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
e2e_screenshots_shared_mode:
name: E2E Shared Mode Service Test (Site Screenshots)
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
strategy:
fail-fast: false
matrix:
tables-mode: [
'Shared V1',
'Shared V2',
]
steps:
- name: checkout
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's/_APP_OPTIONS_ABUSE=disabled/_APP_OPTIONS_ABUSE=enabled/' .env
docker compose up -d
sleep 30
- name: Run Site tests with browser connected in ${{ matrix.tables-mode }} table mode
run: |
echo "Keeping original value of _APP_BROWSER_HOST"
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
fi
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots
+6 -4
View File
@@ -188,16 +188,18 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database;
};
}, ['pools', 'cache']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
CLI::setResource('publisherRedis', function () {
// Stub
});
CLI::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
CLI::setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
CLI::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
+2 -2
View File
@@ -5,7 +5,7 @@
*/
use Appwrite\Auth\Auth;
use Appwrite\Network\Validator\Origin;
use Appwrite\Network\Platform;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
@@ -23,7 +23,7 @@ $console = [
[
'$collection' => ID::custom('platforms'),
'name' => 'Localhost',
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'hostname' => 'localhost',
], // Current host is added on app init
],
+7 -6
View File
@@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '17.0.1',
'version' => '17.0.2',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -134,7 +134,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.10.0',
'version' => '0.10.1',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '8.1.0',
'version' => '8.2.2',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -231,6 +231,7 @@ return [
'gitRepoName' => 'sdk-for-cli',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'master',
'exclude' => [
'services' => [
['name' => 'assistant'],
@@ -250,7 +251,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '17.0.0',
'version' => '17.1.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -358,7 +359,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.13.0',
'version' => '0.14.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -376,7 +377,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '16.0.0',
'version' => '16.1.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
+2 -7
View File
@@ -6,13 +6,8 @@ use Utopia\System\System;
* List of Appwrite Sites templates
*/
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
// TODO: Development override
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN', '');
$url = $protocol . '://' . $hostname;
+94 -38
View File
@@ -21,6 +21,7 @@ use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Redirect;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -60,7 +61,6 @@ use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Host;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
@@ -1182,16 +1182,23 @@ App::get('/v1/account/sessions/oauth2/:provider')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
->param('success', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('failure', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('success', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('failure', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->inject('request')
->inject('response')
->inject('project')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = $request->getProtocol();
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
@@ -1216,12 +1223,20 @@ App::get('/v1/account/sessions/oauth2/:provider')
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$redirectBase = $protocol . '://' . $host;
if ($protocol === 'https' && $port !== '443') {
$redirectBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$redirectBase .= ':' . $port;
}
if (empty($success)) {
$success = $protocol . '://' . $request->getHostname() . $oauthDefaultSuccess;
$success = $redirectBase . $oauthDefaultSuccess;
}
if (empty($failure)) {
$failure = $protocol . '://' . $request->getHostname() . $oauthDefaultFailure;
$failure = $redirectBase . $oauthDefaultFailure;
}
$oauth2 = new $className($appId, $appSecret, $callback, [
@@ -1251,9 +1266,14 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->inject('request')
->inject('response')
->action(function (string $projectId, string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response) {
$domain = $request->getHostname();
$protocol = $request->getProtocol();
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$params = $request->getParams();
$params['project'] = $projectId;
@@ -1262,7 +1282,7 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($protocol . '://' . $domain . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
->redirect($callbackBase . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
. \http_build_query($params));
});
@@ -1282,9 +1302,14 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->inject('request')
->inject('response')
->action(function (string $projectId, string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response) {
$domain = $request->getHostname();
$protocol = $request->getProtocol();
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$params = $request->getParams();
$params['project'] = $projectId;
@@ -1293,7 +1318,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($protocol . '://' . $domain . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
->redirect($callbackBase . '/v1/account/sessions/oauth2/' . $provider . '/redirect?'
. \http_build_query($params));
});
@@ -1317,15 +1342,24 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('request')
->inject('response')
->inject('project')
->inject('platforms')
->inject('devKey')
->inject('user')
->inject('dbForProject')
->inject('geodb')
->inject('queueForEvents')
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) {
$protocol = $request->getProtocol();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
$validateURL = new URL();
$redirect = new Redirect($platforms);
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
@@ -1352,11 +1386,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$state = $defaultState;
}
if (!$validateURL->isValid($state['success'])) {
if ($devKey->isEmpty() && !$redirect->isValid($state['success'])) {
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
}
if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
if ($devKey->isEmpty() && !empty($state['failure']) && !$redirect->isValid($state['failure'])) {
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
}
$failure = [];
@@ -1423,13 +1457,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$name = '';
$nameOAuth = $oauth2->getUserName($accessToken);
$userParam = \json_decode($request->getParam('user'), true);
$userParam = $request->getParam('user');
if (!empty($nameOAuth)) {
$name = $nameOAuth;
} elseif (is_array($userParam)) {
$nameParam = $userParam['name'];
if (is_array($nameParam) && isset($nameParam['firstName']) && isset($nameParam['lastName'])) {
$name = $nameParam['firstName'] . ' ' . $nameParam['lastName'];
} elseif ($userParam !== null) {
$userDecoded = \json_decode($userParam, true);
if (isset($userDecoded['name']['firstName']) && isset($userDecoded['name']['lastName'])) {
$name = $userDecoded['name']['firstName'] . ' ' . $userDecoded['name']['lastName'];
}
}
$email = $oauth2->getUserEmail($accessToken);
@@ -1747,8 +1781,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$state['success']['query'] = URLParser::unparseQuery($query);
$state['success'] = URLParser::unparse($state['success']);
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
@@ -1779,16 +1811,23 @@ App::get('/v1/account/tokens/oauth2/:provider')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
->param('success', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('failure', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('success', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('failure', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->inject('request')
->inject('response')
->inject('project')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = $request->getProtocol();
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
@@ -1813,12 +1852,20 @@ App::get('/v1/account/tokens/oauth2/:provider')
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$redirectBase = $protocol . '://' . $host;
if ($protocol === 'https' && $port !== '443') {
$redirectBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$redirectBase .= ':' . $port;
}
if (empty($success)) {
$success = $protocol . '://' . $request->getHostname() . $oauthDefaultSuccess;
$success = $redirectBase . $oauthDefaultSuccess;
}
if (empty($failure)) {
$failure = $protocol . '://' . $request->getHostname() . $oauthDefaultFailure;
$failure = $redirectBase . $oauthDefaultFailure;
}
$oauth2 = new $className($appId, $appSecret, $callback, [
@@ -1860,7 +1907,7 @@ App::post('/v1/account/tokens/magic-url')
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey'])
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
->inject('request')
->inject('response')
@@ -1961,7 +2008,16 @@ App::post('/v1/account/tokens/magic-url')
$dbForProject->purgeCachedDocument('users', $user->getId());
if (empty($url)) {
$url = $request->getProtocol() . '://' . $request->getHostname() . '/console/auth/magic-url';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$port = $request->getPort();
$callbackBase = $protocol . '://' . $host;
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$url = $callbackBase . '/console/auth/magic-url';
}
$url = Template::parseURL($url);
@@ -3146,7 +3202,7 @@ App::post('/v1/account/recovery')
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('email', '', new Email(), 'User email.')
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients', 'devKey'])
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey'])
->inject('request')
->inject('response')
->inject('user')
@@ -3413,7 +3469,7 @@ App::post('/v1/account/verification')
))
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{userId}')
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients', 'devKey']) // TODO add built-in confirm page
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey']) // TODO add built-in confirm page
->inject('request')
->inject('response')
->inject('project')
+2 -1
View File
@@ -2958,6 +2958,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$collection->getAttribute('attributes'),
$dbForProject->getAdapter()->getMaxIndexLength(),
$dbForProject->getAdapter()->getInternalIndexesKeys(),
$dbForProject->getAdapter()->getSupportForIndexArray(),
);
if (!$validator->isValid($index)) {
@@ -4309,7 +4310,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
}
$data['$id'] = $documentId;
$data['$permissions'] = $permissions;
$data['$permissions'] = $permissions ?? [];
$newDocument = new Document($data);
$operations = 0;
+14 -4
View File
@@ -523,11 +523,16 @@ App::get('/v1/health/queue/databases')
->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('publisher')
->inject('publisherRedis')
->inject('response')
->action(function (string $name, int|string $threshold, Publisher $publisher, Response $response) {
->action(function (string $name, int|string $threshold, Publisher $publisher, ?Publisher $publisherRedis, Response $response) {
$threshold = \intval($threshold);
$size = $publisher->getQueueSize(new Queue($name));
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'databases');
$size = $isRedisFallback
? $publisherRedis->getQueueSize(new Queue($name))
: $publisher->getQueueSize(new Queue($name));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -655,11 +660,16 @@ App::get('/v1/health/queue/migrations')
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('publisher')
->inject('publisherRedis')
->inject('response')
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, ?Publisher $publisherRedis, Response $response) {
$threshold = \intval($threshold);
$size = $publisher->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME));
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'migrations');
$size = $isRedisFallback
? $publisherRedis->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME))
: $publisher->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
+3 -3
View File
@@ -361,7 +361,7 @@ App::post('/v1/migrations/csv')
$hasCompression = $compression !== Compression::NONE;
$migrationId = ID::unique();
$newPath = $deviceForImports->getPath('/' . $migrationId . '_' . $fileId . '.csv');
$newPath = $deviceForImports->getPath($migrationId . '_' . $fileId . '.csv');
if ($hasEncryption || $hasCompression) {
$source = $deviceForFiles->read($path);
@@ -410,8 +410,8 @@ App::post('/v1/migrations/csv')
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => Resource::TYPE_DATABASE,
'statusCounters' => [],
'resourceData' => [],
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => [],
'options' => [
'path' => $newPath,
+2 -2
View File
@@ -8,8 +8,8 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Validator\Event;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Origin;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -1790,7 +1790,7 @@ App::post('/v1/projects/:projectId/platforms')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY, Origin::CLIENT_TYPE_REACT_NATIVE_IOS, Origin::CLIENT_TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
->param('type', null, new WhiteList([Platform::TYPE_WEB, Platform::TYPE_FLUTTER_WEB, Platform::TYPE_FLUTTER_IOS, Platform::TYPE_FLUTTER_ANDROID, Platform::TYPE_FLUTTER_LINUX, Platform::TYPE_FLUTTER_MACOS, Platform::TYPE_FLUTTER_WINDOWS, Platform::TYPE_APPLE_IOS, Platform::TYPE_APPLE_MACOS, Platform::TYPE_APPLE_WATCHOS, Platform::TYPE_APPLE_TVOS, Platform::TYPE_ANDROID, Platform::TYPE_UNITY, Platform::TYPE_REACT_NATIVE_IOS, Platform::TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
+8 -3
View File
@@ -967,6 +967,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
}
/* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -987,6 +988,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
/* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
@@ -1157,7 +1159,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->inject('resourceToken')
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Request $request, Response $response, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
/* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -1175,9 +1177,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
/* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
@@ -1317,6 +1320,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->inject('resourceToken')
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, ?string $token, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
/* @type Document $bucket */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -1334,9 +1338,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($fileSecurity && !$valid) {
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
/* @type Document $file */
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
+2 -2
View File
@@ -11,6 +11,7 @@ use Appwrite\Event\Messaging;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Redirect;
use Appwrite\Platform\Workers\Deletes;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -50,7 +51,6 @@ use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Host;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
@@ -466,7 +466,7 @@ App::post('/v1/teams/:teamId/memberships')
}
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
->param('url', '', fn ($clients, $devKey) => $devKey->isEmpty() ? new Host($clients) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients', 'devKey']) // TODO add our own built-in confirm page
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
->inject('project')
+14 -8
View File
@@ -4,6 +4,7 @@ use Appwrite\Auth\OAuth2\Github as OAuth2Github;
use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Redirect;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -53,7 +54,6 @@ use Utopia\Detector\Detector\Runtime;
use Utopia\Detector\Detector\Strategy;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Host;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
@@ -116,8 +116,10 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$authorizeUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$authorizeUrl = $protocol . '://' . $hostname . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
$action = $isAuthorized ? ['type' => 'logs'] : ['type' => 'authorize', 'url' => $authorizeUrl];
@@ -378,7 +380,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$owner = $github->getOwnerName($providerInstallationId);
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
$providerTargetUrl = $protocol . '://' . $hostname . "/console/project-$region-$projectId/$resourceCollection/$resourceType-$resourceId";
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'pending', $message, $providerTargetUrl, $name);
}
@@ -424,8 +426,8 @@ App::get('/v1/vcs/github/authorize')
type: MethodType::WEBAUTH,
hide: true,
))
->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a successful installation attempt.', true, ['clients'])
->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a failed installation attempt.', true, ['clients'])
->param('success', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a successful installation attempt.', true, ['platforms'])
->param('failure', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a failed installation attempt.', true, ['platforms'])
->inject('request')
->inject('response')
->inject('project')
@@ -437,6 +439,8 @@ App::get('/v1/vcs/github/authorize')
]);
$appName = System::getEnv('_APP_VCS_GITHUB_APP_NAME');
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
if (empty($appName)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GitHub App name is not configured. Please configure VCS (Version Control System) variables in .env file.');
@@ -444,7 +448,7 @@ App::get('/v1/vcs/github/authorize')
$url = "https://github.com/apps/$appName/installations/new?" . \http_build_query([
'state' => $state,
'redirect_uri' => $request->getProtocol() . '://' . $request->getHostname() . "/v1/vcs/github/callback"
'redirect_uri' => $protocol . '://' . $hostname . "/v1/vcs/github/callback"
]);
$response
@@ -494,10 +498,12 @@ App::get('/v1/vcs/github/callback')
}
$region = $project->getAttribute('region', 'default');
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$defaultState = [
'success' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
'failure' => $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$region-$projectId/settings/git-installations",
'success' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
'failure' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
];
$state = \array_merge($defaultState, $state ?? []);
+9 -8
View File
@@ -49,7 +49,6 @@ use Utopia\Logger\Log\User;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
use Utopia\System\System;
use Utopia\Validator\Hostname;
use Utopia\Validator\Text;
Config::setParam('domainVerification', false);
@@ -76,7 +75,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
$errorView = __DIR__ . '/../views/general/error.phtml';
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_DOMAIN', '');
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
if ($rule->isEmpty()) {
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
@@ -267,7 +266,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
if (!$authorized) {
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_DOMAIN');
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
@@ -453,7 +452,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$endpoint = $protocol . '://' . $hostname . "/v1";
@@ -799,6 +798,7 @@ App::init()
->inject('getProjectDB')
->inject('locale')
->inject('localeCodes')
->inject('platforms')
->inject('geodb')
->inject('queueForStatsUsage')
->inject('queueForEvents')
@@ -811,7 +811,7 @@ App::init()
->inject('apiKey')
->inject('httpReferrer')
->inject('httpReferrerSafe')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $platforms, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) {
/*
* Appwrite Router
*/
@@ -1030,11 +1030,12 @@ App::init()
* Skip this check for non-web platforms which are not required to send an origin header
*/
$origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
$originValidator = new Origin($platforms);
if (
!$originValidator->isValid($origin)
&& $devKey->isEmpty()
$devKey->isEmpty()
&& !empty($origin)
&& !$originValidator->isValid($origin)
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
&& $route->getLabel('origin', false) !== '*'
&& empty($request->getHeader('x-appwrite-key', ''))
-26
View File
@@ -129,32 +129,6 @@ App::get('/v1/mock/tests/general/oauth2/failure')
]);
});
App::patch('/v1/mock/functions-v2')
->desc('Update Function Version to V2 (outdated code syntax)')
->groups(['mock', 'api', 'functions'])
->label('scope', 'functions.write')
->label('docs', false)
->param('functionId', '', new UID(), 'Function ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, Response $response, Database $dbForProject) {
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
if (!$isDevelopment) {
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
}
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('version', 'v2'));
$response->noContent();
});
App::post('/v1/mock/api-key-unprefixed')
->desc('Create API Key (without standard prefix)')
->groups(['mock', 'api', 'projects'])
-1
View File
@@ -576,7 +576,6 @@ App::init()
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
+40 -36
View File
@@ -19,6 +19,7 @@ use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\GraphQL\Schema;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Request;
use Executor\Executor;
@@ -79,9 +80,15 @@ App::setResource('localeCodes', function () {
App::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
App::setResource('publisherRedis', function () {
// Stub
});
App::setResource('consumer', function (Group $pools) {
return new BrokerPool(consumer: $pools->get('consumer'));
}, ['pools']);
App::setResource('consumerRedis', function () {
// Stub
});
App::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
@@ -121,11 +128,11 @@ App::setResource('queueForCertificates', function (Publisher $publisher) {
App::setResource('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
App::setResource('clients', function ($request, $console, $project) {
App::setResource('platforms', function (Request $request, Document $console, Document $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => ID::custom('platforms'),
'name' => 'Current Host',
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'hostname' => $request->getHostname(),
], Document::SET_TYPE_APPEND);
@@ -138,39 +145,32 @@ App::setResource('clients', function ($request, $console, $project) {
}
$console->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Origin::CLIENT_TYPE_WEB,
'type' => Platform::TYPE_WEB,
'name' => $hostname,
'hostname' => $hostname,
], Document::SET_TYPE_APPEND);
}
/**
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
*/
$clientsConsole = \array_map(
fn ($node) => $node['hostname'],
\array_filter(
$console->getAttribute('platforms', []),
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
)
);
$clients = $clientsConsole;
$platforms = $project->getAttribute('platforms', []);
foreach ($platforms as $node) {
if (
isset($node['type']) &&
($node['type'] === Origin::CLIENT_TYPE_WEB ||
$node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
!empty($node['hostname'])
) {
$clients[] = $node['hostname'];
}
// Add `exp` and `appwrite-callback-{projectId}` schemes
if (!$project->isEmpty() && $project->getId() !== 'console') {
$project->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Platform::TYPE_SCHEME,
'name' => 'Expo',
'key' => 'exp',
], Document::SET_TYPE_APPEND);
$project->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Platform::TYPE_SCHEME,
'name' => 'Appwrite Callback',
'key' => 'appwrite-callback-' . $project->getId(),
], Document::SET_TYPE_APPEND);
}
return \array_unique($clients);
return [
...$console->getAttribute('platforms', []),
...$project->getAttribute('platforms', []),
];
}, ['request', 'console', 'project']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
@@ -222,7 +222,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
if (APP_MODE_ADMIN !== $mode) {
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} else {
if ($project->isEmpty()) {
$user = new Document([]);
} else {
@@ -232,8 +234,6 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$user = $dbForProject->getDocument('users', Auth::$unique);
}
}
} else {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
}
if (
@@ -264,7 +264,11 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
$user = $dbForProject->getDocument('users', $jwtUserId);
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', $jwtUserId);
} else {
$user = $dbForProject->getDocument('users', $jwtUserId);
}
}
$jwtSessionId = $payload['sessionId'] ?? '';
@@ -950,7 +954,7 @@ App::setResource('httpReferrer', function (Request $request): string {
return $referrer;
}, ['request']);
App::setResource('httpReferrerSafe', function (Request $request, string $httpReferrer, array $clients, Database $dbForPlatform, Document $project, App $utopia): string {
App::setResource('httpReferrerSafe', function (Request $request, string $httpReferrer, array $platforms, Database $dbForPlatform, Document $project, App $utopia): string {
$origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
@@ -963,8 +967,8 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef
}
// Safe if added as web platform
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$originValidator = new Origin($platforms);
if ($originValidator->isValid($request->getOrigin($httpReferrer))) {
return $referrer;
}
@@ -992,4 +996,4 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
return $referrer;
}, ['request', 'httpReferrer', 'clients', 'dbForPlatform', 'project', 'utopia']);
}, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']);
+3 -3
View File
@@ -532,7 +532,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
}
$timelimit = $app->getResource('timelimit');
$console = $app->getResource('console'); /** @var Document $console */
$platforms = $app->getResource('platforms');
$user = $app->getResource('user'); /** @var Document $user */
/*
@@ -557,9 +557,9 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
* Skip this check for non-web platforms which are not required to send an origin header.
*/
$origin = $request->getOrigin();
$originValidator = new Origin(array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
$originValidator = new Origin($platforms);
if (!$originValidator->isValid($origin) && $project->getId() !== 'console') {
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
+1 -1
View File
@@ -15,7 +15,7 @@ $labelClass = '';
$buttons = [];
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
// TODO: remove this later
if (System::getEnv('_APP_ENV') === 'development') {
$hostname = 'localhost';
+8
View File
@@ -247,10 +247,18 @@ Server::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
Server::setResource('publisherRedis', function () {
// Stub
});
Server::setResource('consumer', function (Group $pools) {
return new BrokerPool(consumer: $pools->get('consumer'));
}, ['pools']);
Server::setResource('consumerRedis', function () {
// Stub
});
Server::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
+2 -2
View File
@@ -52,7 +52,7 @@
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "dev-fix-cursor as 0.71.8",
"utopia-php/database": "dev-validate-create-index-array as 0.71.10",
"utopia-php/detector": "0.1.*",
"utopia-php/domains": "0.8.*",
"utopia-php/dsn": "0.2.1",
@@ -62,7 +62,7 @@
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.18.*",
"utopia-php/migration": "0.10.*",
"utopia-php/migration": "0.11.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
Generated
+47 -44
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "077d03afbf1b8b84cc437fc29fe6bf16",
"content-hash": "b7f645a2dfd3313b7202189c133ea954",
"packages": [
{
"name": "adhocore/jwt",
@@ -69,16 +69,16 @@
},
{
"name": "appwrite/appwrite",
"version": "11.1.0",
"version": "15.0.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-for-php.git",
"reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3"
"reference": "deb97b62e0abed8a4fd5c5d48e77365cf89867cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3",
"reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/deb97b62e0abed8a4fd5c5d48e77365cf89867cf",
"reference": "deb97b62e0abed8a4fd5c5d48e77365cf89867cf",
"shasum": ""
},
"require": {
@@ -104,10 +104,10 @@
"support": {
"email": "team@appwrite.io",
"issues": "https://github.com/appwrite/sdk-for-php/issues",
"source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0",
"source": "https://github.com/appwrite/sdk-for-php/tree/15.0.0",
"url": "https://appwrite.io/support"
},
"time": "2024-06-26T07:03:23+00:00"
"time": "2025-05-18T09:47:10+00:00"
},
{
"name": "appwrite/php-clamav",
@@ -3493,16 +3493,16 @@
},
{
"name": "utopia-php/database",
"version": "dev-fix-cursor",
"version": "dev-validate-create-index-array",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "e0e71cd343a1b9066aa006cb7bbffb79aa21fd1a"
"reference": "9e2895b92cb094436973d5f8f30d75014bf03ae6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/e0e71cd343a1b9066aa006cb7bbffb79aa21fd1a",
"reference": "e0e71cd343a1b9066aa006cb7bbffb79aa21fd1a",
"url": "https://api.github.com/repos/utopia-php/database/zipball/9e2895b92cb094436973d5f8f30d75014bf03ae6",
"reference": "9e2895b92cb094436973d5f8f30d75014bf03ae6",
"shasum": ""
},
"require": {
@@ -3543,9 +3543,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/fix-cursor"
"source": "https://github.com/utopia-php/database/tree/validate-create-index-array"
},
"time": "2025-07-02T07:50:22+00:00"
"time": "2025-07-20T08:28:54+00:00"
},
{
"name": "utopia-php/detector",
@@ -3993,20 +3993,20 @@
},
{
"name": "utopia-php/migration",
"version": "0.10.1",
"version": "0.11.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "ea1c585df7ec5f346f061a11581fc9a91679966f"
"reference": "d528a454d5c1ed6564b2843a39ff13297bcdb1af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/ea1c585df7ec5f346f061a11581fc9a91679966f",
"reference": "ea1c585df7ec5f346f061a11581fc9a91679966f",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/d528a454d5c1ed6564b2843a39ff13297bcdb1af",
"reference": "d528a454d5c1ed6564b2843a39ff13297bcdb1af",
"shasum": ""
},
"require": {
"appwrite/appwrite": "11.*",
"appwrite/appwrite": "15.*",
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.1",
@@ -4043,9 +4043,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.10.1"
"source": "https://github.com/utopia-php/migration/tree/0.11.1"
},
"time": "2025-05-26T15:29:19+00:00"
"time": "2025-07-11T13:46:37+00:00"
},
{
"name": "utopia-php/orchestration",
@@ -4810,16 +4810,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.41.9",
"version": "0.41.17",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "61037c1ed9262308cab49c1d760f3278036ab694"
"reference": "ef5e9d952032deecffee7f825a941c40ed4c84b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/61037c1ed9262308cab49c1d760f3278036ab694",
"reference": "61037c1ed9262308cab49c1d760f3278036ab694",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/ef5e9d952032deecffee7f825a941c40ed4c84b8",
"reference": "ef5e9d952032deecffee7f825a941c40ed4c84b8",
"shasum": ""
},
"require": {
@@ -4855,9 +4855,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.41.9"
"source": "https://github.com/appwrite/sdk-generator/tree/0.41.17"
},
"time": "2025-06-27T10:16:17+00:00"
"time": "2025-07-18T08:18:48+00:00"
},
{
"name": "doctrine/annotations",
@@ -5084,16 +5084,16 @@
},
{
"name": "laravel/pint",
"version": "v1.22.1",
"version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7"
"reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7",
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7",
"url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a",
"reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a",
"shasum": ""
},
"require": {
@@ -5104,10 +5104,10 @@
"php": "^8.2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.75.0",
"illuminate/view": "^11.44.7",
"larastan/larastan": "^3.4.0",
"laravel-zero/framework": "^11.36.1",
"friendsofphp/php-cs-fixer": "^3.82.2",
"illuminate/view": "^11.45.1",
"larastan/larastan": "^3.5.0",
"laravel-zero/framework": "^11.45.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.3.1",
"pestphp/pest": "^2.36.0"
@@ -5117,6 +5117,9 @@
],
"type": "project",
"autoload": {
"files": [
"overrides/Runner/Parallel/ProcessFactory.php"
],
"psr-4": {
"App\\": "app/",
"Database\\Seeders\\": "database/seeders/",
@@ -5146,7 +5149,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2025-05-08T08:38:12+00:00"
"time": "2025-07-10T18:09:32+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -5273,16 +5276,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.13.1",
"version": "1.13.3",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"shasum": ""
},
"require": {
@@ -5321,7 +5324,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
},
"funding": [
{
@@ -5329,7 +5332,7 @@
"type": "tidelift"
}
],
"time": "2025-04-29T12:36:36+00:00"
"time": "2025-07-05T12:25:42+00:00"
},
{
"name": "nikic/php-parser",
@@ -8237,9 +8240,9 @@
"aliases": [
{
"package": "utopia-php/database",
"version": "dev-fix-cursor",
"alias": "0.71.8",
"alias_normalized": "0.71.8.0"
"version": "dev-validate-create-index-array",
"alias": "0.71.10",
"alias_normalized": "0.71.10.0"
}
],
"minimum-stability": "stable",
+5 -2
View File
@@ -116,6 +116,7 @@ services:
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
@@ -213,7 +214,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:6.1.2
image: appwrite/console:6.1.12
restart: unless-stopped
networks:
- appwrite
@@ -455,6 +456,7 @@ services:
- redis
- mariadb
environment:
- _APP_BROWSER_HOST
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
@@ -482,6 +484,7 @@ services:
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
@@ -975,7 +978,7 @@ services:
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES,$_APP_SITES_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v5
- OPR_EXECUTOR_RUNTIME_VERSIONS=v5
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
@@ -0,0 +1,16 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
Databases databases = Databases(client);
Document result = await databases.upsertDocument(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>',
data: {},
permissions: ["read("any")"], // (optional)
);
@@ -10,5 +10,5 @@ Databases databases = Databases(client);
DocumentList result = await databases.upsertDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documents: [], // (optional)
documents: [],
);
@@ -0,0 +1,18 @@
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.SetProject("<YOUR_PROJECT_ID>") // Your project ID
.SetSession(""); // The user session to authenticate with
Databases databases = new Databases(client);
Document result = await databases.UpsertDocument(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>",
data: [object],
permissions: ["read("any")"] // optional
);
@@ -12,5 +12,5 @@ Databases databases = new Databases(client);
DocumentList result = await databases.UpsertDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: new List<object>() // optional
documents: new List<object>()
);
@@ -0,0 +1,16 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
const databases = new sdk.Databases(client);
const result = await databases.upsertDocument(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
'<DOCUMENT_ID>', // documentId
{}, // data
["read("any")"] // permissions (optional)
);
@@ -10,5 +10,5 @@ const databases = new sdk.Databases(client);
const result = await databases.upsertDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents (optional)
[] // documents
);
+43
View File
@@ -1,5 +1,48 @@
# Change Log
## 8.2.2
* Fix object comparison logic when pushing settings
* Type generation fixes:
* Dart: Fixed import casing to snake_case, removed `extends Document` and hardcoded attributes, removed unnecessary imports
* Java: Fixed indentation to 4 spaces, updated imports to `java.util.Objects`, fixed enum casing in strict mode as per [Oracle official docs](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)
* Javascript: Updated optional values formatting from `|null` to `| null`
* Kotlin: Fixed indentation to 4 spaces per [Kotlinlang official docs](https://kotlinlang.org/docs/coding-conventions.html#indentation)
* PHP: Fixed indentation to 4 spaces per [PHP Fig official docs](https://www.php-fig.org/psr/psr-2/)
* Swift: Fixed indentation to 4 spaces, improved `decodeIfPresent` usage for optionals, added missing `public` to `init` method
* Typescript: Fixed indentation to 4 spaces per [Typescript coding guidelines](https://github.com/microsoft/TypeScript/wiki/Coding-guidelines)
## 8.2.1
* Added `--with-variables` option to the Sites command for adding/updating environment variables
* Fixed Functions environment variables not being pushed with `--with-variables`
* Removed `awaitPools` when wiping old variables
> **Note:** Storing environment variables in the `vars` attribute of `appwrite.json` is now deprecated due to security risks. Variables are now synced directly from the `.env` file in the root directory of the functions or sites folder.
## 8.2.0
* Add `encrypt` attribute support
* Add improved warnings on attribute recreation and deletion
* Fix `null` parsing error when using create attribute command
* Type generation fixes and improvements:
* Add `--strict` / `-s` flag to `appwrite types` command to generate types in strict mode. This automatically converts the casing of attributes to match the language's naming conventions
* Add automatic package import to `dart` language which uses package detection to import the correct package
* Add `Document` class extension to generated types in `dart` and `js` language to support internal attributes like `$id` and `$collectionId` etc.
* Add proper enum support to `js` language
* Fix indentation in `java`, `kotlin` and `swift` to use 2 spaces instead of 4 for consistency across all languages
* Fix doc comments to use correct syntax in various languages (for eg. `///` instead of `/*`)
* Update enums in `dart` to use lowerCamelCase in `strict` mode as per [constant_identifier_names](https://dart.dev/tools/diagnostics/constant_identifier_names?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=constant_identifier_names)
## 8.1.1
* Fix circular dependency issue due to usage of `success` method in `utils.js` file from `parser.js` file
* Type generation fixes:
* Add ability to generate types directly to a specific file by passing a file path to `appwrite types output_path`, instead of just a directory
* Fix non-required attributes to not be null if default value is provided
* Fix `Models` import error
* Improve formatting and add auto-generated comments
## 8.1.0
* Add multi-region support to `init` command
+8
View File
@@ -1,5 +1,13 @@
# Change Log
## 16.1.0
* Add `gif` support to `ImageFormat` enum
* Add support for `dart38` and `flutter332` runtimes
* Add `upsertDocument()` method to Databases service
* Fix `convertTo()` method in `Document` and `Preferences` models to correctly accept `Map<String, dynamic>`
* Fix `upsertDocuments()` method to have `documents` attribute as required parameter
## 16.0.0
* Add `<REGION>` to doc examples due to the new multi region endpoints
+63 -1
View File
@@ -1 +1,63 @@
# Change Log
# Change Log
## 0.14.0
* Refactor from Newtonsoft.Json to System.Text.Json for serialization/deserialization
* Update package dependencies in `Package.csproj.twig`
* Migrate all serialization/deserialization logic in `Client.cs.twig`, `Query.cs.twig`, and `Extensions.cs.twig`
* Update model attributes from `[JsonProperty]` to `[JsonPropertyName]` in `Model.cs.twig`
* Create new `ObjectToInferredTypesConverter.cs.twig` for proper object type handling
* Replace `JsonConverter` with `JsonConverter<object>` in `ValueClassConverter.cs.twig`
* Update error handling to use `JsonDocument` instead of `JObject`
## 0.13.0
* Add `<REGION>` to doc examples due to the new multi region endpoints
* Add doc examples and methods for bulk api transactions: `createDocuments`, `deleteDocuments` etc.
* Add doc examples, class and methods for new `Sites` service
* Add doc examples, class and methods for new `Tokens` service
* Add enums for `BuildRuntime `, `Adapter`, `Framework`, `DeploymentDownloadType` and `VCSDeploymentType`
* Update enum for `runtimes` with Pythonml312, Dart219, Flutter327 and Flutter329
* Add `token` param to `getFilePreview` and `getFileView` for File tokens usage
* Add `queries` and `search` params to `listMemberships` method
* Remove `search` param from `listExecutions` method
## 0.12.0
* fix: remove content-type from GET requests by @loks0n in https://github.com/appwrite/sdk-for-dotnet/pull/59
* update: min and max are not optional in methods like `UpdateIntegerAttribute` etc.
* chore: regenerate sdk by @ChiragAgg5k in https://github.com/appwrite/sdk-for-dotnet/pull/60
* chore: fix build error by @ChiragAgg5k in https://github.com/appwrite/sdk-for-dotnet/pull/61
## 0.11.0
* Add new push message parameters by @abnegate in https://github.com/appwrite/sdk-for-dotnet/pull/56
## 0.10.0
* fix: chunk upload by @byawitz in https://github.com/appwrite/sdk-for-dotnet/pull/52
## 0.9.0
* Support for Appwrite 1.6
* Added `key` attribute to `Runtime` response model.
* Added `buildSize` attribute to `Deployments` response model.
* Added `scheduledAt` attribute to `Executions` response model.
* Added `scopes` attribute to `Functions` response model.
* Added `specifications` attribute to `Functions` response model.
* Added new response model for `Specifications`.
* Added new response model for `Builds`.
* Added `createJWT()` : Enables creating a JWT using the `userId`.
* Added `listSpecifications()`: Enables listing available runtime specifications.
* Added `deleteExecution()` : Enables deleting executions.
* Added `updateDeploymentBuild()`: Enables cancelling a deployment.
* Added `scheduledAt` parameter to `createExecution()`: Enables creating a delayed execution
#### Breaking changes
You can find the new syntax for breaking changes in the [Appwrite API references](https://appwrite.io/docs/references). Select version `1.6.x`.
* Removed `otp` parameter from `deleteMFAAuthenticator`.
* Added `scopes` parameter for create/update function.
* Renamed `templateBranch` to `templateVersion` in `createFunction()`.
* Renamed `downloadDeployment()` to `getDeploymentDownload()`
> **Please note: This version is compatible with Appwrite 1.6 and later only. If you do not update your Appwrite SDK, old SDKs will not break your app. Appwrite APIs are backwards compatible.**
+12 -1
View File
@@ -1,9 +1,20 @@
# Change Log
## 16.1.1
## 17.0.2
* Add `gif` support to `ImageFormat` enum
* Fix `convertTo()` method in `Document` and `Preferences` models to correctly accept `Map<String, dynamic>`
## 17.0.1
* Fix `devKeys` support by conditionally including credentials during requests
## 17.0.0
* Update `flutter_web_auth_2` dependency to version 4.1.0
* Update `auth.html` example in README.md to align with `flutter_web_auth_2` documentation
* Breaking changes:
* Minimum iOS version supported is now 17.4 due to the updated requirements of `flutter_web_auth_2` version 4.1.0
## 16.1.0
+58 -3
View File
@@ -1,13 +1,68 @@
# Change Log
## 17.1.0
* Add `upsertDocument` method
* Add `dart-3.8` and `flutter-3.32` runtimes
* Add `gif` image format
* Update bulk operation methods to reflect warning message
* Fix file parameter handling in chunked upload method
## 17.0.0
* Add `<REGION>` to doc examples due to the new multi region endpoints
* Add `REGION` to doc examples due to the new multi region endpoints
* Add doc examples and methods for bulk api transactions: `createDocuments`, `deleteDocuments` etc.
* Add doc examples, class and methods for new `Sites` service
* Add doc examples, class and methods for new `Tokens` service
* Add enums for `BuildRuntime `, `Adapter`, `Framework`, `DeploymentDownloadType` and `VCSDeploymentType`
* Add enums for `BuildRuntime`, `Adapter`, `Framework`, `DeploymentDownloadType` and `VCSDeploymentType`
* Updates enum for `runtimes` with Pythonml312, Dart219, Flutter327 and Flutter329
* Add `token` param to `getFilePreview` and `getFileView` for File tokens usage
* Add `queries` and `search` params to `listMemberships` method
* Removes `search` param from `listExecutions` method
* Removes `search` param from `listExecutions` method
## 16.0.0
* Fix: remove content-type from GET requests
* Update (breaking): min and max params are now optional in `updateFloatAttribute` and `updateIntegerAttribute` methods (changes their positioning in method definition)
## 15.0.1
* Remove titles from all function descriptions
* Fix typing for collection "attribute" key
* Remove unnecessary awaits and asyncs
* Ensure `AppwriteException` response is always string
## 15.0.0
* Fix: pong response & chunked upload
## 14.2.0
* Add new push message parameters
## 14.1.0
* Support updating attribute name and size
## 14.0.0
* Support for Appwrite 1.6
* Add `key` attribute to `Runtime` response model.
* Add `buildSize` attribute to `Deployments` response model
* Add `scheduledAt` attribute to `Executions` response model
* Add `scopes` attribute to `Functions` response model
* Add `specifications` attribute to `Functions` response model
* Add new response model for `Specifications`
* Add new response model for `Builds`
* Add `createJWT()` : Enables creating a JWT using the `userId`
* Add `listSpecifications()`: Enables listing available runtime specifications
* Add `deleteExecution()` : Enables deleting executions
* Add `updateDeploymentBuild()`: Enables cancelling a deployment
* Add `scheduledAt` parameter to `createExecution()`: Enables creating a delayed execution
* Breaking changes
* Remove `otp` parameter from `deleteMFAAuthenticator`.
* Add `scopes` parameter for create/update function.
* Rename `templateBranch` to `templateVersion` in `createFunction()`.
* Rename `downloadDeployment()` to `getDeploymentDownload()`
> You can find the new syntax for breaking changes in the [Appwrite API references](https://appwrite.io/docs/references). Select version `1.6.x`.
+5
View File
@@ -1,5 +1,10 @@
# Change log
## 0.10.1
* Fix URL based methods like `getFileViewURL`, `getFilePreviewURL` etc. by adding the missing `projectId` to searchParams
* Add `gif` to ImageFormat enum
## 0.10.0
* Add generate file URL methods like`getFilePreviewURL`, `getFileViewURL` etc.
+20 -17
View File
@@ -267,48 +267,51 @@ class Mapper
}
switch ((!empty($validator)) ? $validator::class : '') {
case 'Appwrite\Network\Validator\CNAME':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
case 'Utopia\Validator\Domain':
case 'Appwrite\Network\Validator\Email':
case 'Appwrite\Auth\Validator\Password':
case 'Appwrite\Event\Validator\Event':
case 'Appwrite\Event\Validator\FunctionEvent':
case 'Appwrite\Network\Validator\CNAME':
case 'Appwrite\Network\Validator\Email':
case 'Appwrite\Network\Validator\Redirect':
case 'Appwrite\Network\Validator\DNS':
case 'Appwrite\Network\Validator\Origin':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
case 'Utopia\Database\Validator\Key':
case 'Utopia\Database\Validator\UID':
case 'Utopia\Validator\Domain':
case 'Utopia\Validator\HexColor':
case 'Utopia\Validator\Host':
case 'Utopia\Validator\IP':
case 'Utopia\Database\Validator\Key':
case 'Utopia\Validator\Origin':
case 'Appwrite\Auth\Validator\Password':
case 'Utopia\Validator\Text':
case 'Utopia\Database\Validator\UID':
case 'Utopia\Validator\URL':
case 'Utopia\Validator\WhiteList':
default:
$type = Type::string();
break;
case 'Utopia\Database\Validator\Authorization':
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
case 'Utopia\Database\Validator\Queries\Documents':
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
case 'Utopia\Database\Validator\Permissions':
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
case 'Utopia\Database\Validator\Queries':
case 'Utopia\Database\Validator\Roles':
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
case 'Appwrite\Utopia\Database\Validator\Queries\Users':
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
case 'Utopia\Database\Validator\Authorization':
case 'Utopia\Database\Validator\Permissions':
case 'Utopia\Database\Validator\Queries':
case 'Utopia\Database\Validator\Queries\Documents':
case 'Utopia\Database\Validator\Roles':
$type = Type::listOf(Type::string());
break;
case 'Utopia\Validator\Boolean':
+159
View File
@@ -0,0 +1,159 @@
<?php
namespace Appwrite\Network;
class Platform
{
public const TYPE_UNKNOWN = 'unknown';
public const TYPE_WEB = 'web';
public const TYPE_FLUTTER_IOS = 'flutter-ios';
public const TYPE_FLUTTER_ANDROID = 'flutter-android';
public const TYPE_FLUTTER_MACOS = 'flutter-macos';
public const TYPE_FLUTTER_WINDOWS = 'flutter-windows';
public const TYPE_FLUTTER_LINUX = 'flutter-linux';
public const TYPE_FLUTTER_WEB = 'flutter-web';
public const TYPE_APPLE_IOS = 'apple-ios';
public const TYPE_APPLE_MACOS = 'apple-macos';
public const TYPE_APPLE_WATCHOS = 'apple-watchos';
public const TYPE_APPLE_TVOS = 'apple-tvos';
public const TYPE_ANDROID = 'android';
public const TYPE_UNITY = 'unity';
public const TYPE_REACT_NATIVE_IOS = 'react-native-ios';
public const TYPE_REACT_NATIVE_ANDROID = 'react-native-android';
public const TYPE_SCHEME = 'scheme';
public const SCHEME_HTTP = 'http';
public const SCHEME_HTTPS = 'https';
public const SCHEME_CHROME_EXTENSION = 'chrome-extension';
public const SCHEME_FIREFOX_EXTENSION = 'moz-extension';
public const SCHEME_SAFARI_EXTENSION = 'safari-web-extension';
public const SCHEME_EDGE_EXTENSION = 'ms-browser-extension';
public const SCHEME_IOS = 'appwrite-ios';
public const SCHEME_MACOS = 'appwrite-macos';
public const SCHEME_WATCHOS = 'appwrite-watchos';
public const SCHEME_TVOS = 'appwrite-tvos';
public const SCHEME_ANDROID = 'appwrite-android';
public const SCHEME_WINDOWS = 'appwrite-windows';
public const SCHEME_LINUX = 'appwrite-linux';
/**
* @var array<string, string> Map scheme types to user-friendly platform names.
*/
private static array $names = [
self::SCHEME_HTTP => 'Web',
self::SCHEME_HTTPS => 'Web',
self::SCHEME_IOS => 'iOS',
self::SCHEME_MACOS => 'macOS',
self::SCHEME_WATCHOS => 'watchOS',
self::SCHEME_TVOS => 'tvOS',
self::SCHEME_ANDROID => 'Android',
self::SCHEME_WINDOWS => 'Windows',
self::SCHEME_LINUX => 'Linux',
self::SCHEME_CHROME_EXTENSION => 'Web (Chrome Extension)',
self::SCHEME_FIREFOX_EXTENSION => 'Web (Firefox Extension)',
self::SCHEME_SAFARI_EXTENSION => 'Web (Safari Extension)',
self::SCHEME_EDGE_EXTENSION => 'Web (Edge Extension)',
];
/**
* Get user-friendly platform name from a scheme.
*
* @param string|null $scheme
* @return string Empty string if scheme is not found.
*/
public static function getNameByScheme(?string $scheme): string
{
return self::$names[$scheme] ?? '';
}
public static function getHostnames(array $platforms): array
{
$hostnames = [];
foreach ($platforms as $platform) {
$type = strtolower($platform['type'] ?? self::TYPE_UNKNOWN);
$hostname = strtolower($platform['hostname'] ?? '');
$key = strtolower($platform['key'] ?? '');
switch ($type) {
case self::TYPE_WEB:
case self::TYPE_FLUTTER_WEB:
if (!empty($hostname)) {
$hostnames[] = $hostname;
}
break;
case self::TYPE_FLUTTER_IOS:
case self::TYPE_FLUTTER_ANDROID:
case self::TYPE_FLUTTER_MACOS:
case self::TYPE_FLUTTER_WINDOWS:
case self::TYPE_FLUTTER_LINUX:
case self::TYPE_ANDROID:
case self::TYPE_APPLE_IOS:
case self::TYPE_APPLE_MACOS:
case self::TYPE_APPLE_WATCHOS:
case self::TYPE_APPLE_TVOS:
case self::TYPE_REACT_NATIVE_IOS:
case self::TYPE_REACT_NATIVE_ANDROID:
case self::TYPE_UNITY:
if (!empty($key)) {
$hostnames[] = $key;
}
break;
default:
break;
}
}
return array_unique($hostnames);
}
public static function getSchemes(array $platforms): array
{
$schemes = [];
foreach ($platforms as $platform) {
$type = strtolower($platform['type'] ?? self::TYPE_UNKNOWN);
$scheme = strtolower($platform['key'] ?? '');
switch ($type) {
case self::TYPE_SCHEME:
if (!empty($scheme) && preg_match('/^[a-z][a-z0-9+.-]*$/', $scheme)) {
$schemes[] = $scheme;
}
break;
case self::TYPE_WEB:
case self::TYPE_FLUTTER_WEB:
$schemes[] = self::SCHEME_HTTP;
$schemes[] = self::SCHEME_HTTPS;
break;
case self::TYPE_FLUTTER_IOS:
case self::TYPE_APPLE_IOS:
case self::TYPE_REACT_NATIVE_IOS:
$schemes[] = self::SCHEME_IOS;
break;
case self::TYPE_FLUTTER_ANDROID:
case self::TYPE_ANDROID:
case self::TYPE_REACT_NATIVE_ANDROID:
$schemes[] = self::SCHEME_ANDROID;
break;
case self::TYPE_FLUTTER_MACOS:
case self::TYPE_APPLE_MACOS:
$schemes[] = self::SCHEME_MACOS;
break;
case self::TYPE_FLUTTER_WINDOWS:
case self::TYPE_UNITY:
$schemes[] = self::SCHEME_WINDOWS;
break;
case self::TYPE_FLUTTER_LINUX:
$schemes[] = self::SCHEME_LINUX;
break;
case self::TYPE_APPLE_WATCHOS:
$schemes[] = self::SCHEME_WATCHOS;
break;
case self::TYPE_APPLE_TVOS:
$schemes[] = self::SCHEME_TVOS;
break;
default:
break;
}
}
return array_unique($schemes);
}
}
+80 -117
View File
@@ -2,150 +2,91 @@
namespace Appwrite\Network\Validator;
use Appwrite\Network\Platform;
use Utopia\Validator;
use Utopia\Validator\Hostname;
class Origin extends Validator
{
public const CLIENT_TYPE_UNKNOWN = 'unknown';
public const CLIENT_TYPE_WEB = 'web';
public const CLIENT_TYPE_FLUTTER_IOS = 'flutter-ios';
public const CLIENT_TYPE_FLUTTER_ANDROID = 'flutter-android';
public const CLIENT_TYPE_FLUTTER_MACOS = 'flutter-macos';
public const CLIENT_TYPE_FLUTTER_WINDOWS = 'flutter-windows';
public const CLIENT_TYPE_FLUTTER_LINUX = 'flutter-linux';
public const CLIENT_TYPE_FLUTTER_WEB = 'flutter-web';
public const CLIENT_TYPE_APPLE_IOS = 'apple-ios';
public const CLIENT_TYPE_APPLE_MACOS = 'apple-macos';
public const CLIENT_TYPE_APPLE_WATCHOS = 'apple-watchos';
public const CLIENT_TYPE_APPLE_TVOS = 'apple-tvos';
public const CLIENT_TYPE_ANDROID = 'android';
public const CLIENT_TYPE_UNITY = 'unity';
public const CLIENT_TYPE_REACT_NATIVE_IOS = 'react-native-ios';
public const CLIENT_TYPE_REACT_NATIVE_ANDROID = 'react-native-android';
public const SCHEME_TYPE_HTTP = 'http';
public const SCHEME_TYPE_HTTPS = 'https';
public const SCHEME_TYPE_IOS = 'appwrite-ios';
public const SCHEME_TYPE_MACOS = 'appwrite-macos';
public const SCHEME_TYPE_WATCHOS = 'appwrite-watchos';
public const SCHEME_TYPE_TVOS = 'appwrite-tvos';
public const SCHEME_TYPE_ANDROID = 'appwrite-android';
public const SCHEME_TYPE_WINDOWS = 'appwrite-windows';
public const SCHEME_TYPE_LINUX = 'appwrite-linux';
protected array $hostnames = [];
protected array $schemes = [];
protected ?string $scheme = null;
protected ?string $host = null;
protected string $origin = '';
/**
* @var array
* Constructor
*
* @param array<\Utopia\Database\Document> $platforms
*/
protected $platforms = [
self::SCHEME_TYPE_HTTP => 'Web',
self::SCHEME_TYPE_HTTPS => 'Web',
self::SCHEME_TYPE_IOS => 'iOS',
self::SCHEME_TYPE_MACOS => 'macOS',
self::SCHEME_TYPE_WATCHOS => 'watchOS',
self::SCHEME_TYPE_TVOS => 'tvOS',
self::SCHEME_TYPE_ANDROID => 'Android',
self::SCHEME_TYPE_WINDOWS => 'Windows',
self::SCHEME_TYPE_LINUX => 'Linux',
];
/**
* @var array
*/
protected $clients = [
];
/**
* @var string
*/
protected $client = self::CLIENT_TYPE_UNKNOWN;
/**
* @var string
*/
protected $host = '';
/**
* @param string $target
*/
public function __construct($platforms)
public function __construct(array $platforms)
{
foreach ($platforms as $platform) {
$type = (isset($platform['type'])) ? $platform['type'] : '';
switch ($type) {
case self::CLIENT_TYPE_WEB:
case self::CLIENT_TYPE_FLUTTER_WEB:
$this->clients[] = (isset($platform['hostname'])) ? $platform['hostname'] : '';
break;
case self::CLIENT_TYPE_FLUTTER_IOS:
case self::CLIENT_TYPE_FLUTTER_ANDROID:
case self::CLIENT_TYPE_FLUTTER_MACOS:
case self::CLIENT_TYPE_FLUTTER_WINDOWS:
case self::CLIENT_TYPE_FLUTTER_LINUX:
case self::CLIENT_TYPE_ANDROID:
case self::CLIENT_TYPE_APPLE_IOS:
case self::CLIENT_TYPE_APPLE_MACOS:
case self::CLIENT_TYPE_APPLE_WATCHOS:
case self::CLIENT_TYPE_APPLE_TVOS:
case self::CLIENT_TYPE_REACT_NATIVE_IOS:
case self::CLIENT_TYPE_REACT_NATIVE_ANDROID:
$this->clients[] = (isset($platform['key'])) ? $platform['key'] : '';
break;
default:
# code...
break;
}
}
$this->hostnames = Platform::getHostnames($platforms);
$this->schemes = Platform::getSchemes($platforms);
}
public function getDescription(): string
{
if (!\array_key_exists($this->client, $this->platforms)) {
return 'Unsupported platform';
}
return 'Invalid Origin. Register your new client (' . $this->host . ') as a new '
. $this->platforms[$this->client] . ' platform on your project console dashboard';
}
/**
* Check if Origin has been allowed
* for access to the API
*
* @param mixed $origin
*
* Check if Origin is valid.
* @param mixed $origin The Origin URI.
* @return bool
*/
public function isValid($origin): bool
{
if (!is_string($origin)) {
$this->origin = $origin;
$this->scheme = null;
$this->host = null;
if (!is_string($origin) || empty($origin)) {
return false;
}
$scheme = \parse_url($origin, PHP_URL_SCHEME);
$host = \parse_url($origin, PHP_URL_HOST);
$this->scheme = $this->parseScheme($origin);
$this->host = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
$this->host = $host;
$this->client = $scheme;
$webPlatforms = [
Platform::SCHEME_HTTP,
Platform::SCHEME_HTTPS,
Platform::SCHEME_CHROME_EXTENSION,
Platform::SCHEME_FIREFOX_EXTENSION,
Platform::SCHEME_SAFARI_EXTENSION,
Platform::SCHEME_EDGE_EXTENSION,
];
if (in_array($this->scheme, $webPlatforms, true)) {
$validator = new Hostname($this->hostnames);
return $validator->isValid($this->host);
}
if (empty($host)) {
if (!empty($this->scheme) && in_array($this->scheme, $this->schemes, true)) {
return true;
}
$validator = new Hostname($this->clients);
return false;
}
return $validator->isValid($host);
/**
* Get Description
* @return string
*/
public function getDescription(): string
{
$platform = $this->scheme ? Platform::getNameByScheme($this->scheme) : '';
$host = $this->host ? '(' . $this->host . ')' : '';
if (empty($this->host) && empty($this->scheme)) {
return 'Invalid Origin.';
}
if (empty($platform)) {
return 'Invalid Scheme. The scheme used (' . $this->scheme . ') in the Origin (' . $this->origin . ') is not supported. If you are using a custom scheme, please change it to `appwrite-callback-<PROJECT_ID>`';
}
return 'Invalid Origin. Register your new client ' . $host . ' as a new '
. $platform . ' platform on your project console dashboard';
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
@@ -155,13 +96,35 @@ class Origin extends Validator
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
/**
* Parses the scheme from a URI string.
*
* @param string $uri The URI string to parse.
* @return string|null The extracted scheme string (e.g., "http", "exp", "mailto")
*/
public function parseScheme(string $uri): ?string
{
$uri = trim($uri);
if ($uri === '') {
return null; // No scheme in empty string
}
$scheme = parse_url($uri, PHP_URL_SCHEME);
if ($scheme === false) {
if (preg_match('/^([a-z][a-z0-9+.-]*):/i', $uri, $matches)) {
return $matches[1];
} else {
return null;
}
} else {
return $scheme;
}
}
}
@@ -0,0 +1,25 @@
<?php
namespace Appwrite\Network\Validator;
use Appwrite\Network\Platform;
class Redirect extends Origin
{
/**
* Get Description
* @return string
*/
public function getDescription(): string
{
$platform = Platform::getNameByScheme($this->scheme);
$host = $this->host ? '(' . $this->host . ')' : '';
if (empty($this->host) && empty($this->scheme)) {
return 'Invalid URI.';
}
return 'Invalid URI. Register your new client ' . $host . ' as a new '
. $platform . ' platform on your project console dashboard';
}
}
@@ -63,8 +63,8 @@ class XList extends Base
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
$maxCpus = System::getEnv('_APP_COMPUTE_CPUS', 0);
$maxMemory = System::getEnv('_APP_COMPUTE_MEMORY', 0);
// Only add specs that are within the limits set by environment variables
// Treat 0 as no limit
@@ -264,7 +264,7 @@ class Builds extends Action
->setParam('deploymentId', $deployment->getId());
if ($deployment->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -275,8 +275,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -392,7 +391,7 @@ class Builds extends Action
Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $deploymentId), '', $stdout, $stderr);
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -525,8 +524,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -658,7 +656,7 @@ class Builds extends Action
$err = null;
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -809,7 +807,7 @@ class Builds extends Action
]);
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -858,9 +856,7 @@ class Builds extends Action
$adapter = $resource->getAttribute('adapter', '');
if (empty($adapter)) {
$resource->setAttribute('adapter', $detection->getName());
$resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$resource = $dbForProject->updateDocument('sites', $resource->getId(), new Document(['adapter' => $detection->getName(), 'fallbackFile' => $detection->getFallbackFile() ?? '']));
$deployment->setAttribute('adapter', $detection->getName());
$deployment->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
@@ -959,8 +955,9 @@ class Builds extends Action
$config['sleep'] = $framework['screenshotSleep'];
}
$browserEndpoint = Config::getParam('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
$fetchResponse = $client->fetch(
url: 'http://appwrite-browser:3000/v1/screenshots',
url: $browserEndpoint . '/screenshots',
method: 'POST',
body: $config
);
@@ -1062,8 +1059,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -1077,14 +1073,38 @@ class Builds extends Action
Console::success("Build id: $deploymentId created");
/** Set auto deploy */
$activateBuild = false;
if ($deployment->getAttribute('activate') === true) {
$resource->setAttribute('live', true);
// Check if current active deployment started later than this deployment
$resource = $dbForProject->getDocument($resource->getCollection(), $resource->getId());
$currentActiveDeploymentId = $resource->getAttribute('deploymentId', '');
if (!empty($currentActiveDeploymentId)) {
$currentActiveDeployment = $dbForProject->getDocument('deployments', $currentActiveDeploymentId);
if (!$currentActiveDeployment->isEmpty()) {
$currentActiveStartTime = $currentActiveDeployment->getCreatedAt();
$deploymentStartTime = $deployment->getCreatedAt();
// Skip auto-activation if current active deployment started later than deployment that is being activated
if ($currentActiveStartTime < $deploymentStartTime) {
$activateBuild = true;
} else {
Console::info('Skipping auto-activation as current deployment is more recent');
}
}
} else {
$activateBuild = true;
}
}
if ($activateBuild) {
switch ($resource->getCollection()) {
case 'functions':
$resource->setAttribute('deploymentId', $deployment->getId());
$resource->setAttribute('deploymentInternalId', $deployment->getSequence());
$resource->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt());
$resource = $dbForProject->updateDocument('functions', $resource->getId(), $resource);
$resource = $dbForProject->updateDocument('functions', $resource->getId(), new Document([
'live' => true,
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentCreatedAt' => $deployment->getCreatedAt(),
]));
$queries = [
Query::equal('projectInternalId', [$project->getSequence()]),
@@ -1098,19 +1118,21 @@ class Builds extends Action
$rulesUpdated = false;
$dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment, &$rulesUpdated) {
$rulesUpdated = true;
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}, $queries);
break;
case 'sites':
$resource->setAttribute('deploymentId', $deployment->getId());
$resource->setAttribute('deploymentInternalId', $deployment->getSequence());
$resource->setAttribute('deploymentScreenshotDark', $deployment->getAttribute('screenshotDark', ''));
$resource->setAttribute('deploymentScreenshotLight', $deployment->getAttribute('screenshotLight', ''));
$resource->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt());
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$resource = $dbForProject->updateDocument('sites', $resource->getId(), new Document([
'live' => true,
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
'deploymentScreenshotDark' => $deployment->getAttribute('screenshotDark', ''),
'deploymentScreenshotLight' => $deployment->getAttribute('screenshotLight', ''),
'deploymentCreatedAt' => $deployment->getCreatedAt(),
]));
$queries = [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('type', ['deployment']),
@@ -1121,10 +1143,10 @@ class Builds extends Action
];
$dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}, $queries);
break;
@@ -1166,11 +1188,10 @@ class Builds extends Action
'region' => $project->getAttribute('region')
]));
} catch (Duplicate $err) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $ruleId, new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}
$queries = [
@@ -1183,10 +1204,10 @@ class Builds extends Action
];
$dbForPlatform->foreach('rules', function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getSequence());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getSequence(),
]));
}, $queries);
}
}
@@ -1201,7 +1222,7 @@ class Builds extends Action
->trigger();
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -1224,7 +1245,7 @@ class Builds extends Action
Console::error($th->getTraceAsString());
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
}
@@ -1256,8 +1277,7 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
}
$queueForRealtime
@@ -1283,6 +1303,8 @@ class Builds extends Action
protected function sendUsage(Document $resource, Document $deployment, Document $project, StatsUsage $queue): void
{
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
switch ($deployment->getAttribute('status')) {
case 'ready':
$queue
@@ -1447,7 +1469,7 @@ class Builds extends Action
$name = "{$resourceName} ({$projectName})";
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$projectId = $project->getId();
$region = $project->getAttribute('region', 'default');
@@ -1511,4 +1533,22 @@ class Builds extends Action
}
}
}
private function cancelDeployment(string $deploymentId, Database $dbForProject, Realtime $queueForRealtime)
{
Console::info('Build has been canceled');
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$logs = $deployment->getAttribute('buildLogs', '');
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Build has been canceled. \n";
$deployment->setAttribute('buildLogs', $logs);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
}
}
@@ -63,8 +63,8 @@ class XList extends Base
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
$maxCpus = System::getEnv('_APP_COMPUTE_CPUS', 0);
$maxMemory = System::getEnv('_APP_COMPUTE_MEMORY', 0);
// Only add specs that are within the limits set by environment variables
// Treat 0 as no limit
@@ -37,6 +37,7 @@ class Action extends UtopiaAction
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
return [
'bucket' => $bucket,
'file' => $file,
+3 -2
View File
@@ -272,6 +272,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
$gitUrl = 'git@github.com:aw-tests/' . $language['gitRepoName'] . '.git';
}
$repoBranch = $language['repoBranch'] ?? 'main';
if ($git && !empty($gitUrl)) {
\exec('rm -rf ' . $target . ' && \
mkdir -p ' . $target . ' && \
@@ -279,8 +280,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
git init && \
git remote add origin ' . $gitUrl . ' && \
git fetch origin && \
git checkout main || git checkout -b main && \
git pull origin main && \
git checkout ' . $repoBranch . ' || git checkout -b ' . $repoBranch . ' && \
git pull origin ' . $repoBranch . ' && \
git checkout ' . $gitBranch . ' || git checkout -b ' . $gitBranch . ' && \
git fetch origin ' . $gitBranch . ' || git push -u origin ' . $gitBranch . ' && \
git pull origin ' . $gitBranch . ' && \
@@ -26,6 +26,7 @@ abstract class ScheduleBase extends Action
protected array $schedules = [];
protected BrokerPool $publisher;
protected ?BrokerPool $publisherRedis = null;
private ?Histogram $collectSchedulesTelemetryDuration = null;
private ?Gauge $collectSchedulesTelemetryCount = null;
@@ -72,6 +73,13 @@ abstract class ScheduleBase extends Action
Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started');
$this->publisher = new BrokerPool($pools->get('publisher'));
try {
$this->publisherRedis = new BrokerPool($pools->get('publisherRedis'));
} catch (\Throwable) {
$this->publisherRedis = null;
}
$this->scheduleTelemetryCount = $telemetry->createGauge('task.schedule.count');
$this->collectSchedulesTelemetryDuration = $telemetry->createHistogram('task.schedule.collect_schedules.duration', 's');
$this->collectSchedulesTelemetryCount = $telemetry->createGauge('task.schedule.collect_schedules.count');
@@ -6,6 +6,7 @@ use Appwrite\Event\Func;
use Swoole\Coroutine as Co;
use Utopia\Database\Database;
use Utopia\Pools\Group;
use Utopia\System\System;
class ScheduleExecutions extends ScheduleBase
{
@@ -29,9 +30,16 @@ class ScheduleExecutions extends ScheduleBase
protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void
{
$queueForFunctions = new Func($this->publisher);
$intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds');
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'functions');
$queueForFunctions = new Func(
$isRedisFallback
? $this->publisherRedis
: $this->publisher
);
foreach ($this->schedules as $schedule) {
if (!$schedule['active']) {
$dbForPlatform->deleteDocument(
@@ -8,6 +8,7 @@ use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Pools\Group;
use Utopia\System\System;
class ScheduleFunctions extends ScheduleBase
{
@@ -90,7 +91,13 @@ class ScheduleFunctions extends ScheduleBase
$this->updateProjectAccess($schedule['project'], $dbForPlatform);
$queueForFunctions = new Func($this->publisher);
$isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'functions');
$queueForFunctions = new Func(
$isRedisFallback
? $this->publisherRedis
: $this->publisher
);
$queueForFunctions
->setType('schedule')
+4 -1
View File
@@ -1117,6 +1117,9 @@ class Deletes extends Action
): void {
$start = \microtime(true);
$deleteBatchSize = Database::DELETE_BATCH_SIZE;
$deleteBatchSize = 500; // TODO: Set right value in DB library after investigation
/**
* deleteDocuments uses a cursor, we need to add a unique order by field or use default
*/
@@ -1124,7 +1127,7 @@ class Deletes extends Action
$count = $database->deleteDocuments(
$collection,
$queries,
Database::DELETE_BATCH_SIZE,
$deleteBatchSize,
$callback
);
} catch (Throwable $th) {
@@ -314,6 +314,7 @@ class Migrations extends Action
$migration->getAttribute('resourceType')
);
}
$destination->shutDown();
$source->shutDown();
@@ -360,6 +360,7 @@ class OpenAPI3 extends Format
break;
case 'Utopia\Validator\Host':
case 'Utopia\Validator\URL':
case 'Appwrite\Network\Validator\Redirect':
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'url';
$node['schema']['x-example'] = 'https://example.com';
@@ -385,6 +385,7 @@ class Swagger2 extends Format
break;
case 'Utopia\Validator\Host':
case 'Utopia\Validator\URL':
case 'Appwrite\Network\Validator\Redirect':
$node['type'] = $validator->getType();
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
+16 -3
View File
@@ -26,7 +26,20 @@ class URL
'fragment' => '',
];
return \array_merge($default, \parse_url($url));
$parsed = \parse_url($url);
if (is_array($parsed)) {
return \array_merge($default, $parsed);
}
// see if $url is just a scheme
if (preg_match('/^([a-z][a-z0-9+.-]*):/i', $url, $matches)) {
$scheme = $matches[1];
return \array_merge($default, [
'scheme' => $scheme
]);
}
throw new \InvalidArgumentException('Invalid URL: ' . $url);
}
/**
@@ -55,9 +68,9 @@ class URL
$parts['user'] = isset($url['user']) ? $url['user'] : '';
$parts['pass'] = isset($url['pass']) ? ':' . $url['pass'] : '';
$parts['pass'] = !empty($url['pass']) ? ':' . $url['pass'] : '';
$parts['pass'] = ($parts['user'] || $parts['pass']) ? $parts['pass'] . '@' : '';
$parts['pass'] = ($parts['user'] || !empty($parts['pass'])) ? $parts['pass'] . '@' : '';
$parts['path'] = isset($url['path']) ? $url['path'] : '';
+4 -4
View File
@@ -86,8 +86,8 @@ class Comment
$i = 0;
foreach ($projects as $projectId => $project) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN'));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$text .= "## {$project['name']}\n\n";
$text .= "Project ID: `{$projectId}`\n\n";
@@ -200,8 +200,8 @@ class Comment
public function generatImage(string $pathLight, string $pathDark, string $alt, int $width): string
{
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN'));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
$imageLight = $protocol . '://' . $hostname . $pathLight;
$imageDark = $protocol . '://' . $hostname . $pathDark;
+3 -3
View File
@@ -675,7 +675,8 @@ class UsageTest extends Scope
[
'functionId' => 'unique()',
'name' => 'Test',
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'vars' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
@@ -697,8 +698,7 @@ class UsageTest extends Scope
$this->assertNotEmpty($response['body']['$id']);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true,
]);
$this->assertNotEmpty($deploymentId);
+53 -15
View File
@@ -1343,8 +1343,12 @@ trait DatabasesBase
'type' => 'key',
'attributes' => ['actors'],
]);
$this->assertEquals(202, $actorsArray['headers']['status-code']);
/**
* Disabling array index due to mysql bug
* @link https://github.com/appwrite/appwrite/pull/10169
*/
$this->assertEquals(400, $actorsArray['headers']['status-code']);
$this->assertEquals('Indexing an array attribute is temporarily disabled', $actorsArray['body']['message']);
$twoLevelsArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
'content-type' => 'application/json',
@@ -1357,9 +1361,10 @@ trait DatabasesBase
'orders' => ['DESC', 'DESC'],
]);
$this->assertEquals(202, $twoLevelsArray['headers']['status-code']);
$this->assertEquals('DESC', $twoLevelsArray['body']['orders'][0]);
$this->assertEquals(null, $twoLevelsArray['body']['orders'][1]); // Overwrite by API (array)
$this->assertEquals(400, $twoLevelsArray['headers']['status-code']);
$this->assertEquals('Indexing an array attribute is temporarily disabled', $twoLevelsArray['body']['message']);
//$this->assertEquals('DESC', $twoLevelsArray['body']['orders'][0]);
//$this->assertEquals(null, $twoLevelsArray['body']['orders'][1]); // Overwrite by API (array)
$unknown = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
'content-type' => 'application/json',
@@ -1384,7 +1389,8 @@ trait DatabasesBase
'attributes' => ['integers'], // array attribute
'orders' => ['DESC'], // Check order is removed in API
]);
$this->assertEquals(202, $index1['headers']['status-code']);
$this->assertEquals(400, $index1['headers']['status-code']);
$this->assertEquals('Indexing an array attribute is temporarily disabled', $index1['body']['message']);
$index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
'content-type' => 'application/json',
@@ -1395,7 +1401,8 @@ trait DatabasesBase
'type' => 'key',
'attributes' => ['integers'], // array attribute
]);
$this->assertEquals(202, $index2['headers']['status-code']);
$this->assertEquals(400, $index2['headers']['status-code']);
$this->assertEquals('Indexing an array attribute is temporarily disabled', $index2['body']['message']);
/**
* Create Indexes by worker
@@ -1409,7 +1416,7 @@ trait DatabasesBase
]), []);
$this->assertIsArray($movies['body']['indexes']);
$this->assertCount(8, $movies['body']['indexes']);
$this->assertCount(4, $movies['body']['indexes']);
$this->assertEquals($titleIndex['body']['key'], $movies['body']['indexes'][0]['key']);
$this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']);
$this->assertEquals($releaseWithDate1['body']['key'], $movies['body']['indexes'][2]['key']);
@@ -1476,14 +1483,15 @@ trait DatabasesBase
'attributes' => ['actors'],
'lengths' => [120]
]);
$this->assertEquals(202, $create['headers']['status-code']);
$this->assertEquals(400, $create['headers']['status-code']);
$this->assertEquals('Indexing an array attribute is temporarily disabled', $create['body']['message']);
$index = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/indexes/lengthOverrideTestIndex", [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]);
$this->assertEquals([Database::ARRAY_INDEX_LENGTH], $index['body']['lengths']);
// $index = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/indexes/lengthOverrideTestIndex", [
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// 'x-appwrite-key' => $this->getProject()['apiKey']
// ]);
// $this->assertEquals([Database::ARRAY_INDEX_LENGTH], $index['body']['lengths']);
// Test case for count of lengths greater than attributes (should throw 400)
$create = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", [
@@ -1996,6 +2004,36 @@ trait DatabasesBase
],
]);
$this->assertEquals(2, $documents['body']['total']);
// test without passing permissions
$document = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'title' => 'Thor: Ragnarok',
'releaseYear' => 2000
]
]);
$this->assertEquals(200, $document['headers']['status-code']);
$this->assertEquals('Thor: Ragnarok', $document['body']['title']);
$document = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(200, $document['headers']['status-code']);
$deleteResponse = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $documentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(204, $deleteResponse['headers']['status-code']);
}
/**
@@ -21,8 +21,8 @@ class FunctionsConsoleClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -39,8 +39,8 @@ class FunctionsConsoleClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test Failure',
'execute' => ['some-random-string'],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
]);
$this->assertEquals(400, $function2['headers']['status-code']);
@@ -453,7 +453,7 @@ class FunctionsConsoleClientTest extends Scope
{
$function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Variable E2E Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -481,7 +481,7 @@ class FunctionsConsoleClientTest extends Scope
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -502,7 +502,7 @@ class FunctionsConsoleClientTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Download Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -511,7 +511,7 @@ class FunctionsConsoleClientTest extends Scope
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -43,8 +43,8 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -52,8 +52,7 @@ class FunctionsCustomClientTest extends Scope
'timeout' => 10,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -84,13 +83,12 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-fn'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -107,8 +105,8 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('Test', $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('Node.js', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('22', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
$this->assertEquals(System::getEnv('_APP_REGION', 'default'), $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
@@ -142,10 +140,10 @@ class FunctionsCustomClientTest extends Scope
*/
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'name' => 'Test guest execution',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'vars' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
@@ -154,8 +152,7 @@ class FunctionsCustomClientTest extends Scope
'timeout' => 10,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-fn'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -175,8 +172,8 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
@@ -193,15 +190,14 @@ class FunctionsCustomClientTest extends Scope
*/
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'name' => 'Test synchronous execution',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-fn'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -214,11 +210,11 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
$this->assertEquals('Test', $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals('Test synchronous execution', $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('Node.js', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('22', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
$this->assertEquals(System::getEnv('_APP_REGION', 'default'), $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
@@ -239,12 +235,12 @@ class FunctionsCustomClientTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'entrypoint' => 'index.js'
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -340,7 +336,7 @@ class FunctionsCustomClientTest extends Scope
'limit' => 5,
'offset' => 2,
'useCases' => ['databases'],
'runtimes' => ['node-16.0']
'runtimes' => ['node-22']
]);
$this->assertEquals(200, $templates['headers']['status-code']);
@@ -352,7 +348,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertContains($template['useCases'][0], ['databases']);
}
$this->assertContains('node-16.0', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
$this->assertContains('node-22', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
/**
* Test for FAILURE
@@ -35,7 +35,7 @@ class FunctionsCustomServerTest extends Scope
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Specs function',
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'specification' => $specifications['body']['specifications'][0]['slug']
]);
$this->assertEquals(201, $function['headers']['status-code']);
@@ -50,7 +50,7 @@ class FunctionsCustomServerTest extends Scope
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Specs function',
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'specification' => 'cheap-please'
]);
$this->assertEquals(400, $function['headers']['status-code']);
@@ -64,8 +64,8 @@ class FunctionsCustomServerTest extends Scope
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'buckets.*.create',
'buckets.*.delete',
@@ -79,7 +79,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertNotEmpty($function['body']['$id']);
$this->assertEquals('Test', $function['body']['name']);
$this->assertEquals('php-8.0', $function['body']['runtime']);
$this->assertEquals('node-22', $function['body']['runtime']);
$this->assertEquals(true, $dateValidator->isValid($function['body']['$createdAt']));
$this->assertEquals(true, $dateValidator->isValid($function['body']['$updatedAt']));
$this->assertEquals('', $function['body']['deploymentId']);
@@ -180,7 +180,7 @@ class FunctionsCustomServerTest extends Scope
// Test search runtime
$functions = $this->listFunctions([
'search' => 'php-8.0'
'search' => 'node-22'
]);
$this->assertEquals($functions['headers']['status-code'], 200);
@@ -193,8 +193,8 @@ class FunctionsCustomServerTest extends Scope
$this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test 2',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'buckets.*.create',
'buckets.*.delete',
@@ -286,8 +286,8 @@ class FunctionsCustomServerTest extends Scope
],
'schedule' => '0 0 1 1 *',
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
]);
$dateValidator = new DatetimeValidator();
@@ -322,8 +322,8 @@ class FunctionsCustomServerTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -338,8 +338,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey'],
'x-sdk-language' => 'cli',
], [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -363,7 +362,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $starterTemplate['headers']['status-code']);
$phpRuntime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) {
return $runtime['name'] === 'php-8.0';
return $runtime['name'] === 'node-22';
}))[0];
// If this fails, the template has variables, and this test needs to be updated
@@ -373,7 +372,7 @@ class FunctionsCustomServerTest extends Scope
[
'functionId' => ID::unique(),
'name' => $starterTemplate['body']['name'],
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'execute' => $starterTemplate['body']['permissions'],
'entrypoint' => $phpRuntime['entrypoint'],
'events' => $starterTemplate['body']['events'],
@@ -514,7 +513,7 @@ class FunctionsCustomServerTest extends Scope
$functionId = $data['functionId'];
$deployment = $this->createDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -522,7 +521,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertEquals('waiting', $deployment['body']['status']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt']));
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
$this->assertEquals('index.js', $deployment['body']['entrypoint']);
$deploymentIdActive = $deployment['body']['$id'] ?? '';
@@ -533,7 +532,7 @@ class FunctionsCustomServerTest extends Scope
}, 50000, 500);
$deployment = $this->createDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => 'false'
]);
@@ -573,7 +572,7 @@ class FunctionsCustomServerTest extends Scope
$functionId = $data['functionId'];
$deployment = $this->createDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => 'false'
]);
@@ -582,7 +581,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt']));
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
$this->assertEquals('index.js', $deployment['body']['entrypoint']);
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
@@ -595,12 +594,12 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('canceled', $deployment['body']['status']);
/**
* Build worker still runs the build.
* 30s sleep gives worker enough time to finish build.
* After build finished, it should still be canceled, not ready.
*/
\sleep(30);
// Ensures worker got eventually aware of cancellation and reacted properly
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertStringContainsString('Build has been canceled.', $deployment['body']['buildLogs']);
});
$deployment = $this->getDeployment($functionId, $deploymentId);
@@ -618,7 +617,7 @@ class FunctionsCustomServerTest extends Scope
*/
$functionId = $data['functionId'];
$folder = 'php-large';
$folder = 'large';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
@@ -633,13 +632,13 @@ class FunctionsCustomServerTest extends Scope
];
$id = '';
while (!feof($handle)) {
$curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'php-large-fx.tar.gz');
$curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'large-fx.tar.gz');
$headers['content-range'] = 'bytes ' . ($counter * $chunkSize) . '-' . min(((($counter * $chunkSize) + $chunkSize) - 1), $size - 1) . '/' . $size;
if (!empty($id)) {
$headers['x-appwrite-id'] = $id;
}
$largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge($headers, $this->getHeaders()), [
'entrypoint' => 'index.php',
'entrypoint' => 'index.js',
'code' => $curlFile,
'activate' => true,
'commands' => 'cp blue.mp4 copy.mp4 && ls -al' // +7MB buildSize
@@ -652,7 +651,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $largeTag['headers']['status-code']);
$this->assertNotEmpty($largeTag['body']['$id']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($largeTag['body']['$createdAt']));
$this->assertEquals('index.php', $largeTag['body']['entrypoint']);
$this->assertEquals('index.js', $largeTag['body']['entrypoint']);
$this->assertGreaterThan(1024 * 1024 * 5, $largeTag['body']['sourceSize']); // ~7MB video file
$this->assertLessThan(1024 * 1024 * 10, $largeTag['body']['sourceSize']); // ~7MB video file
@@ -903,8 +902,8 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString($data['deploymentId'], $execution['body']['responseBody']);
$this->assertStringContainsString('Test1', $execution['body']['responseBody']);
$this->assertStringContainsString('http', $execution['body']['responseBody']);
$this->assertStringContainsString('PHP', $execution['body']['responseBody']);
$this->assertStringContainsString('8.0', $execution['body']['responseBody']);
$this->assertStringContainsString('Node.js', $execution['body']['responseBody']);
$this->assertStringContainsString('22', $execution['body']['responseBody']);
$this->assertStringContainsString('Global Variable Value', $execution['body']['responseBody']);
// $this->assertStringContainsString('êä', $execution['body']['responseBody']); // tests unknown utf-8 chars
$this->assertNotEmpty($execution['body']['errors']);
@@ -1017,8 +1016,8 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertStringContainsString('Test1', $execution['body']['responseBody']);
$this->assertStringContainsString('http', $execution['body']['responseBody']);
$this->assertStringContainsString('PHP', $execution['body']['responseBody']);
$this->assertStringContainsString('8.0', $execution['body']['responseBody']);
$this->assertStringContainsString('Node.js', $execution['body']['responseBody']);
$this->assertStringContainsString('22', $execution['body']['responseBody']);
// $this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
$this->assertLessThan(1.500, $execution['body']['duration']);
@@ -1120,8 +1119,8 @@ class FunctionsCustomServerTest extends Scope
'users.*.update.email',
],
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'specification' => Specification::S_1VCPU_1GB,
]);
@@ -1148,8 +1147,8 @@ class FunctionsCustomServerTest extends Scope
'users.*.update.email',
],
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'specification' => Specification::S_1VCPU_512MB,
]);
@@ -1178,8 +1177,8 @@ class FunctionsCustomServerTest extends Scope
'users.*.update.email',
],
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'specification' => 's-2vcpu-512mb', // Invalid specification
]);
@@ -1236,9 +1235,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test php-8.0',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test timeout execution',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [],
'schedule' => '',
'timeout' => 5, // Should timeout after 5 seconds
@@ -1280,12 +1279,12 @@ class FunctionsCustomServerTest extends Scope
*/
public function provideCustomExecutions(): array
{
// Most disabled to keep tests fast
return [
['folder' => 'php-fn', 'name' => 'php-8.0', 'entrypoint' => 'index.php', 'runtimeName' => 'PHP', 'runtimeVersion' => '8.0'],
['folder' => 'node', 'name' => 'node-18.0', 'entrypoint' => 'index.js', 'runtimeName' => 'Node.js', 'runtimeVersion' => '18.0'],
['folder' => 'python', 'name' => 'python-3.9', 'entrypoint' => 'main.py', 'runtimeName' => 'Python', 'runtimeVersion' => '3.9'],
['folder' => 'ruby', 'name' => 'ruby-3.1', 'entrypoint' => 'main.rb', 'runtimeName' => 'Ruby', 'runtimeVersion' => '3.1'],
// Swift and Dart disabled on purpose, as it's very slow.
// ['folder' => 'php-fn', 'name' => 'php-8.0', 'entrypoint' => 'index.php', 'runtimeName' => 'PHP', 'runtimeVersion' => '8.0'],
['folder' => 'node', 'name' => 'node-22', 'entrypoint' => 'index.js', 'runtimeName' => 'Node.js', 'runtimeVersion' => '22'],
// ['folder' => 'python', 'name' => 'python-3.9', 'entrypoint' => 'main.py', 'runtimeName' => 'Python', 'runtimeVersion' => '3.9'],
// ['folder' => 'ruby', 'name' => 'ruby-3.1', 'entrypoint' => 'main.rb', 'runtimeName' => 'Ruby', 'runtimeVersion' => '3.1'],
// [ 'folder' => 'dart', 'name' => 'dart-2.15', 'entrypoint' => 'main.dart', 'runtimeName' => 'Dart', 'runtimeVersion' => '2.15' ],
// [ 'folder' => 'swift', 'name' => 'swift-5.5', 'entrypoint' => 'index.swift', 'runtimeName' => 'Swift', 'runtimeVersion' => '5.5' ],
];
@@ -1324,27 +1323,14 @@ class FunctionsCustomServerTest extends Scope
]);
$execution = $this->createExecution($functionId, [
'body' => 'foobar',
'async' => 'false'
]);
$output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
$this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals($runtimeName, $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals($runtimeVersion, $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals('variable', $output['CUSTOM_VARIABLE']);
$this->assertEmpty($output['APPWRITE_FUNCTION_USER_ID']);
$this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
$this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
$this->assertStringContainsString('Amazing Function Log', $execution['body']['logs']);
$this->assertEquals('OK', $execution['body']['responseBody']);
$this->assertEmpty($execution['body']['logs']);
$this->assertEmpty($execution['body']['errors']);
$executionId = $execution['body']['$id'] ?? '';
@@ -1357,7 +1343,8 @@ class FunctionsCustomServerTest extends Scope
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('Amazing Function Log', $executions['body']['executions'][0]['logs']);
$this->assertEquals(200, $executions['body']['executions'][0]['responseStatusCode']);
$this->assertEmpty($executions['body']['executions'][0]['responseBody']);
$this->cleanupFunction($functionId);
}
@@ -1366,15 +1353,14 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-response'),
'code' => $this->packageFunction('binary-response'),
'activate' => true
]);
@@ -1415,15 +1401,14 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-request'),
'code' => $this->packageFunction('binary-request'),
'activate' => true
]);
@@ -1461,49 +1446,6 @@ class FunctionsCustomServerTest extends Scope
$this->cleanupFunction($functionId);
}
public function testv2Function()
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP V2',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'events' => [],
'timeout' => 15,
]);
$variable = $this->client->call(Client::METHOD_PATCH, '/mock/functions-v2', [
'content-type' => 'application/json',
'origin' => 'http://localhost',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-mode' => 'admin',
], [
'functionId' => $functionId
]);
$this->assertEquals(204, $variable['headers']['status-code']);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-v2'),
'activate' => true
]);
$execution = $this->createExecution($functionId, [
'body' => 'foobar',
'async' => 'false'
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(true, $output['v2Woks']);
$this->cleanupFunction($functionId);
}
public function testGetRuntimes()
{
$runtimes = $this->client->call(Client::METHOD_GET, '/functions/runtimes', array_merge([
@@ -1531,17 +1473,16 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Event executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Event executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
],
'timeout' => 15,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-event'),
'code' => $this->packageFunction('event-handler'),
'activate' => true
]);
@@ -1584,17 +1525,16 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Scopes executions',
'commands' => 'bash setup.sh && composer install',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Scopes executions',
'commands' => 'bash setup.sh && npm install',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'scopes' => ['users.read'],
'timeout' => 15,
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-scopes'),
'code' => $this->packageFunction('dynamic-api-key'),
'activate' => true,
]);
@@ -1643,16 +1583,15 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Cookie executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
]);
$this->assertNotEmpty($functionId);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-cookie'),
'code' => $this->packageFunction('cookies'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId);
@@ -1685,9 +1624,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Cookie executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1695,8 +1634,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-cookie'),
'code' => $this->packageFunction('cookies'),
'activate' => true
]);
@@ -1743,9 +1681,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1753,8 +1691,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-response'),
'code' => $this->packageFunction('binary-response'),
'activate' => true
]);
@@ -1778,9 +1715,9 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'name' => 'Test Binary executions',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1788,8 +1725,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php-binary-request'),
'code' => $this->packageFunction('binary-request'),
'activate' => true
]);
@@ -1816,8 +1752,8 @@ class FunctionsCustomServerTest extends Scope
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
]);
@@ -1851,8 +1787,8 @@ class FunctionsCustomServerTest extends Scope
$function1Id = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1860,8 +1796,8 @@ class FunctionsCustomServerTest extends Scope
$function2Id = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test2',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'execute' => ['any']
]);
@@ -1892,7 +1828,7 @@ class FunctionsCustomServerTest extends Scope
{
$function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Logging Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -1908,7 +1844,7 @@ class FunctionsCustomServerTest extends Scope
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -1985,7 +1921,7 @@ class FunctionsCustomServerTest extends Scope
// Check if the function specifications are correctly set in builds
$function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Specification Test',
'entrypoint' => 'index.js',
'logging' => false,
@@ -2001,7 +1937,7 @@ class FunctionsCustomServerTest extends Scope
$functionId = $functionId = $function['body']['$id'] ?? '';
$deploymentId = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -2027,7 +1963,7 @@ class FunctionsCustomServerTest extends Scope
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Duplicate Deployment Test',
'entrypoint' => 'index.js',
'commands' => ''
@@ -2035,7 +1971,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertNotEmpty($functionId);
$deploymentId1 = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId1);
@@ -2045,7 +1981,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('APPWRITE_FUNCTION_ID', $execution['body']['responseBody']);
$function = $this->updateFunction($functionId, [
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Duplicate Deployment Test',
'entrypoint' => 'index.js',
'commands' => 'rm index.js && mv maintenance.js index.js'
@@ -2089,13 +2025,12 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'runtime' => 'php-8.0',
'runtime' => 'node-22',
'name' => 'Re-activate Test',
'entrypoint' => 'index.php',
'entrypoint' => 'index.js',
]);
$this->assertNotEmpty($functionId);
$function = $this->getFunction($functionId);
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertArrayHasKey('latestDeploymentId', $function['body']);
@@ -2106,7 +2041,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEmpty($function['body']['latestDeploymentStatus']);
$deploymentId1 = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php-cookie'),
'code' => $this->packageFunction('cookies'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId1);
@@ -2124,7 +2059,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('cookieValue', $execution['body']['responseBody']);
$deploymentId2 = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId2);
@@ -2192,8 +2127,8 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Error Pages',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'commands' => 'cd non-existing-directory',
'execute' => ['any']
@@ -2203,8 +2138,7 @@ class FunctionsCustomServerTest extends Scope
$proxyClient->setEndpoint('http://' . $domain);
$deployment = $this->createDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -2221,8 +2155,7 @@ class FunctionsCustomServerTest extends Scope
// canceled deployment
$deployment = $this->createDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -2250,8 +2183,8 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Error Pages',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'commands' => '',
'execute' => ['users']
@@ -2262,7 +2195,7 @@ class FunctionsCustomServerTest extends Scope
$proxyClient->setEndpoint('http://' . $domain);
$deploymentId = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId);
@@ -2284,8 +2217,8 @@ class FunctionsCustomServerTest extends Scope
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Error Pages',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
'commands' => '',
'execute' => ['any']
@@ -2296,7 +2229,7 @@ class FunctionsCustomServerTest extends Scope
$proxyClient->setEndpoint('http://' . $domain);
$deploymentId = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
$this->assertNotEmpty($deploymentId);
@@ -25,8 +25,8 @@ class FunctionsScheduleTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -36,8 +36,7 @@ class FunctionsScheduleTest extends Scope
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -77,14 +76,13 @@ class FunctionsScheduleTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
'logging' => true,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -179,15 +177,14 @@ class FunctionsScheduleTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
'logging' => true,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -26,8 +26,8 @@ class FunctionsClientTest extends Scope
'variables' => [
'functionId' => ID::unique(),
'name' => 'Test Function',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'execute' => [Role::any()->toString()],
]
];
@@ -96,7 +96,7 @@ class FunctionsClientTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
'code' => $this->packageFunction('php')
'code' => $this->packageFunction('basic')
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', [
@@ -26,8 +26,8 @@ class FunctionsServerTest extends Scope
'variables' => [
'functionId' => ID::unique(),
'name' => 'Test Function',
'entrypoint' => 'index.php',
'runtime' => 'php-8.0',
'entrypoint' => 'index.js',
'runtime' => 'node-22',
'execute' => [Role::any()->toString()],
]
];
@@ -95,7 +95,7 @@ class FunctionsServerTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
@@ -805,13 +805,12 @@ trait MigrationsBase
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php'
'runtime' => 'node-22',
'entrypoint' => 'index.js'
]);
$deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -855,8 +854,8 @@ trait MigrationsBase
$this->assertEquals($functionId, $response['body']['$id']);
$this->assertEquals('Test', $response['body']['name']);
$this->assertEquals('php-8.0', $response['body']['runtime']);
$this->assertEquals('index.php', $response['body']['entrypoint']);
$this->assertEquals('node-22', $response['body']['runtime']);
$this->assertEquals('index.js', $response['body']['entrypoint']);
$this->assertEventually(function () use ($functionId) {
@@ -4637,7 +4637,6 @@ class ProjectsConsoleClientTest extends Scope
'failure' => 'https://example.com'
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertStringContainsString('Invalid `success` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']);
/** Test oauth2 with devKey and now get oauth2 is disabled */
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [
@@ -4660,7 +4659,6 @@ class ProjectsConsoleClientTest extends Scope
'url' => 'https://example.com',
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals('Invalid `url` param: URL host must be one of: localhost, appwrite.io, *.appwrite.io', $response['body']['message']);
/** Test hostname in Magic URL with devKey */
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', [
@@ -30,8 +30,6 @@ class ProjectsCustomServerTest extends Scope
'domain' => $testId . '-api.appwrite.test',
]);
\var_dump($response);
$this->assertEquals(201, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
+2 -2
View File
@@ -227,7 +227,7 @@ trait ProxyBase
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'runtime' => 'node-22',
'name' => 'Proxy Function',
'entrypoint' => 'index.js',
'commands' => '',
@@ -244,7 +244,7 @@ trait ProxyBase
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), [
'code' => $this->packageFunction('node'),
'code' => $this->packageFunction('basic'),
'activate' => 'true'
]);
+2 -16
View File
@@ -16,13 +16,6 @@ trait RealtimeBase
$projectId = $this->getProject()['$id'];
}
$headers = array_merge(
[
"Origin" => "appwrite.test",
],
$headers
);
$query = [
"project" => $projectId,
"channels" => $channels,
@@ -49,7 +42,7 @@ trait RealtimeBase
public function testConnectionFailureMissingChannels(): void
{
$client = $this->getWebsocket();
$client = $this->getWebsocket([]);
$payload = json_decode($client->receive(), true);
$this->assertArrayHasKey("type", $payload);
@@ -64,14 +57,7 @@ trait RealtimeBase
public function testConnectionFailureUnknownProject(): void
{
$client = new WebSocketClient(
"ws://appwrite-traefik/v1/realtime?project=123",
[
"headers" => [
"Origin" => "appwrite.test",
],
]
);
$client = $this->getWebsocket(projectId: '123');
$payload = json_decode($client->receive(), true);
$this->assertArrayHasKey("type", $payload);
@@ -524,8 +524,8 @@ class RealtimeConsoleClientTest extends Scope
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'users.*.create',
'users.*.delete',
@@ -564,8 +564,7 @@ class RealtimeConsoleClientTest extends Scope
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'code' => $this->packageFunction('basic'),
'activate' => true
]);
@@ -1285,10 +1285,10 @@ class RealtimeCustomClientTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'functionId' => ID::unique(),
'name' => 'Test',
'name' => 'Test timeout execution',
'execute' => ['users'],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
@@ -1302,7 +1302,6 @@ class RealtimeCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('timeout'),
'activate' => true
]);
@@ -14,6 +14,9 @@ class SitesConsoleClientTest extends Scope
use SideConsole;
use SitesBase;
/**
* @group screenshots
*/
public function testSiteScreenshot(): void
{
$siteId = $this->setupSite([
@@ -6,6 +6,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Utopia\System\System;
class SitesCustomClientTest extends Scope
{
@@ -121,6 +122,7 @@ class SitesCustomClientTest extends Scope
* Test for SUCCESS
*/
$template = $this->getTemplate('starter-for-react');
$hostname = System::getEnv('_APP_DOMAIN') ?: '';
$this->assertEquals(200, $template['headers']['status-code']);
$this->assertIsArray($template['body']);
$this->assertEquals('starter-for-react', $template['body']['key']);
@@ -129,8 +131,8 @@ class SitesCustomClientTest extends Scope
$this->assertEquals('github', $template['body']['vcsProvider']);
$this->assertEquals('Simple React application integrated with Appwrite SDK.', $template['body']['tagline']);
$this->assertIsArray($template['body']['frameworks']);
$this->assertEquals('http://localhost/images/sites/templates/starter-for-react-dark.png', $template['body']['screenshotDark']);
$this->assertEquals('http://localhost/images/sites/templates/starter-for-react-light.png', $template['body']['screenshotLight']);
$this->assertEquals('http://'. $hostname . '/images/sites/templates/starter-for-react-dark.png', $template['body']['screenshotDark']);
$this->assertEquals('http://' . $hostname . '/images/sites/templates/starter-for-react-light.png', $template['body']['screenshotLight']);
/**
* Test for FAILURE
@@ -937,12 +937,12 @@ class SitesCustomServerTest extends Scope
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('canceled', $deployment['body']['status']);
/**
* Build worker still runs the build.
* 30s sleep gives worker enough time to finish build.
* After build finished, it should still be canceled, not ready.
*/
\sleep(30);
// Ensures worker got eventually aware of cancellation and reacted properly
$this->assertEventually(function () use ($siteId, $deploymentId) {
$deployment = $this->getDeployment($siteId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertStringContainsString('Build has been canceled.', $deployment['body']['buildLogs']);
});
$deployment = $this->getDeployment($siteId, $deploymentId);
+86
View File
@@ -5,6 +5,8 @@ namespace Tests\E2E\Services\Tokens;
use CURLFile;
use Tests\E2E\Client;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
trait TokensBase
{
@@ -275,4 +277,88 @@ trait TokensBase
$this->assertEquals($image->getImageHeight(), $original->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
}
public function testFileAccessWithFileSecurity(): void
{
$bucket = $this->client->call(
Client::METHOD_POST,
'/storage/buckets',
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],
[
'name' => 'Test Bucket',
'bucketId' => ID::unique(),
'fileSecurity' => true,
'allowedFileExtensions' => ['jpg', 'png', 'jfif'],
]
);
$this->assertEquals(201, $bucket['headers']['status-code']);
$this->assertNotEmpty($bucket['body']['$id']);
$bucketId = $bucket['body']['$id'];
$file = $this->client->call(
Client::METHOD_POST,
'/storage/buckets/' . $bucketId . '/files',
[
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
],
[
'fileId' => ID::unique(),
'permissions' => [ Permission::read(Role::label('devrel')) ],
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
]
);
$fileId = $file['body']['$id'];
$token = $this->client->call(
Client::METHOD_POST,
'/tokens/buckets/' . $bucketId . '/files/' . $fileId,
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]
);
$jwtToken = $token['body']['secret'];
$endpoints = ['preview', 'view', 'download'];
foreach ($endpoints as $endpoint) {
$response = $this->client->call(
Client::METHOD_GET,
"/storage/buckets/$bucketId/files/$fileId/$endpoint",
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
],
[
'token' => $jwtToken
]
);
$this->assertNotEmpty($response['body']);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('image/png', $response['headers']['content-type']);
if ($endpoint === 'download') {
$image = new \Imagick();
$image->readImageBlob($response['body']);
$original = new \Imagick(__DIR__ . '/../../../resources/logo.png');
$this->assertEquals($original->getImageWidth(), $image->getImageWidth());
$this->assertEquals($original->getImageHeight(), $image->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
}
}
}
}
@@ -399,8 +399,8 @@ class WebhooksCustomServerTest extends Scope
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
@@ -439,8 +439,8 @@ class WebhooksCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'execute' => [Role::any()->toString()],
'vars' => [
'key1' => 'value1',
@@ -496,7 +496,7 @@ class WebhooksCustomServerTest extends Scope
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'entrypoint' => 'index.js',
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
'activate' => true
]);
+46
View File
@@ -0,0 +1,46 @@
module.exports = async(context) => {
context.log('log-works');
context.error('error-log-works');
if(context.req.headers['x-appwrite-user-jwt']) {
context.log('jwt-is-valid');
} else {
context.log('jwt-is-invalid');
}
if(context.req.path === '/custom-response') {
const code = +(context.req.query['code'] || '200');
const body = context.req.query['body'] || '';
return context.res.send(body, code);
}
context.log('body-is-' + (context.req.body ?? ''));
context.log('custom-header-is-' + (context.req.headers['x-custom-header'] ?? ''));
context.log('method-is-' + (context.req.method ?? '').toLowerCase());
context.log('path-is-' + (context.req.path ?? ''));
context.log('user-is-' + (context.req.headers['x-appwrite-user-id'] ?? ''));
const statusCode = context.req.query['code'] || '200';
return context.res.json({
'APPWRITE_FUNCTION_ID' : process.env.APPWRITE_FUNCTION_ID ?? '',
'APPWRITE_FUNCTION_NAME' : process.env.APPWRITE_FUNCTION_NAME ?? '',
'APPWRITE_FUNCTION_DEPLOYMENT' : process.env.APPWRITE_FUNCTION_DEPLOYMENT ?? '',
'APPWRITE_FUNCTION_TRIGGER' : context.req.headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' : process.env.APPWRITE_FUNCTION_RUNTIME_NAME,
'APPWRITE_FUNCTION_RUNTIME_VERSION' : process.env.APPWRITE_FUNCTION_RUNTIME_VERSION,
'APPWRITE_VERSION' : process.env.APPWRITE_VERSION ?? '',
'APPWRITE_REGION' : process.env.APPWRITE_REGION ?? '',
'UNICODE_TEST' : "êä",
'GLOBAL_VARIABLE' : process.env.GLOBAL_VARIABLE ?? '',
'APPWRITE_FUNCTION_EVENT' : context.req.headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' : context.req.headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' : context.req.headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' : process.env.APPWRITE_FUNCTION_PROJECT_ID,
'APPWRITE_FUNCTION_MEMORY' : process.env.APPWRITE_FUNCTION_MEMORY,
'APPWRITE_FUNCTION_CPUS' : process.env.APPWRITE_FUNCTION_CPUS,
'CUSTOM_VARIABLE' : process.env.CUSTOM_VARIABLE
}, +statusCode);
}
@@ -0,0 +1,6 @@
const crypto = require('crypto')
module.exports = async(context) => {
const hash = crypto.createHash('md5').update(context.req.bodyBinary).digest("hex")
return context.res.send(hash);
};
@@ -0,0 +1,4 @@
module.exports = async(context) => {
const bytes = Buffer.from(Uint8Array.from([0, 10, 255]));
return context.res.binary(bytes);
};
@@ -0,0 +1,3 @@
module.exports = async(context) => {
return context.res.send(context.req.headers['cookie'] ?? '');
};
-21
View File
@@ -1,21 +0,0 @@
import 'dart:io' show Platform;
Future<dynamic> main(final context) async {
context.log('Amazing Function Log');
response.json({
'APPWRITE_FUNCTION_ID' : Platform.environment['APPWRITE_FUNCTION_ID'] ?? '',
'APPWRITE_FUNCTION_NAME' : Platform.environment['APPWRITE_FUNCTION_NAME'] ?? '',
'APPWRITE_FUNCTION_DEPLOYMENT' : Platform.environment['APPWRITE_FUNCTION_DEPLOYMENT'] ?? '',
'APPWRITE_FUNCTION_TRIGGER' : context.req.headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' : Platform.environment['APPWRITE_FUNCTION_RUNTIME_NAME'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' : Platform.environment['APPWRITE_FUNCTION_RUNTIME_VERSION'] ?? '',
'APPWRITE_FUNCTION_EVENT' : context.req.headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' : context.req.headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' : context.req.headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' : Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'] ?? '',
'CUSTOM_VARIABLE' : request.variables['CUSTOM_VARIABLE']
});
}
@@ -0,0 +1,15 @@
const sdk = require('node-appwrite');
module.exports = async(context) => {
const client = new sdk.Client();
client.setEndpoint(process.env.APPWRITE_FUNCTION_API_ENDPOINT);
client.setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID);
client.setKey(context.req.headers['x-appwrite-key']);
const users = new sdk.Users(client);
const response = await users.list();
context.log(JSON.stringify(response));
return context.res.json(response);
};
@@ -0,0 +1,31 @@
{
"name": "dynamic-api-key",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dynamic-api-key",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"node-appwrite": "^17.0.0"
}
},
"node_modules/node-appwrite": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-17.0.0.tgz",
"integrity": "sha512-5Moi5ENPnoAfU1/6CZP9K2NTuB6Nm3dSyhokno+24RDuP7czjXCdwzfeyjmyHieggbrLkN89AYSOv9W1XkCL9w==",
"license": "BSD-3-Clause",
"dependencies": {
"node-fetch-native-with-agent": "1.7.2"
}
},
"node_modules/node-fetch-native-with-agent": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz",
"integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==",
"license": "MIT"
}
}
}
@@ -0,0 +1,15 @@
{
"name": "dynamic-api-key",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"node-appwrite": "^17.0.0"
}
}
@@ -1,6 +1,7 @@
ENDPOINT="$APPWRITE_FUNCTION_API_ENDPOINT/users"
PROJECT_ID="$APPWRITE_FUNCTION_PROJECT_ID"
API_KEY="$APPWRITE_FUNCTION_API_KEY"
apk add curl
curl -v -X GET $ENDPOINT -H "x-appwrite-project: $PROJECT_ID" -H "x-appwrite-key: $API_KEY"
@@ -0,0 +1,5 @@
module.exports = async(context) => {
context.log(context.req.body.$id);
context.log(context.req.body.name);
return context.res.empty();
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = async(context) => {
return context.res.empty();
};
+1 -19
View File
@@ -1,21 +1,3 @@
module.exports = async(context) => {
context.log('Amazing Function Log');
return context.res.json({
'APPWRITE_FUNCTION_ID' : process.env.APPWRITE_FUNCTION_ID ?? '',
'APPWRITE_FUNCTION_NAME' : process.env.APPWRITE_FUNCTION_NAME ?? '',
'APPWRITE_FUNCTION_DEPLOYMENT' : process.env.APPWRITE_FUNCTION_DEPLOYMENT ?? '',
'APPWRITE_FUNCTION_TRIGGER' : context.req.headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' : process.env.APPWRITE_FUNCTION_RUNTIME_NAME,
'APPWRITE_FUNCTION_RUNTIME_VERSION' : process.env.APPWRITE_FUNCTION_RUNTIME_VERSION,
'APPWRITE_FUNCTION_EVENT' : context.req.headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' : context.req.bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' : context.req.headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' : context.req.headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' : process.env.APPWRITE_FUNCTION_PROJECT_ID,
'APPWRITE_FUNCTION_MEMORY' : process.env.APPWRITE_FUNCTION_MEMORY,
'APPWRITE_FUNCTION_CPUS' : process.env.APPWRITE_FUNCTION_CPUS,
'CUSTOM_VARIABLE' : process.env.CUSTOM_VARIABLE
});
return context.res.send('OK');
}
@@ -1,6 +0,0 @@
<?php
return function ($context) {
$hash = md5($context->req->bodyBinary);
return $context->res->send($hash);
};
@@ -1,6 +0,0 @@
<?php
return function ($context) {
$bytes = pack('C*', ...[0, 10, 255]);
return $context->res->binary($bytes);
};
@@ -1,5 +0,0 @@
<?php
return function ($context) {
return $context->res->send($context->req->headers['cookie'] ?? '');
};
@@ -1,8 +0,0 @@
<?php
return function ($context) {
$context->log($context->req->body['$id']);
$context->log($context->req->body['name']);
return $context->res->empty();
};
@@ -1,23 +0,0 @@
<?php
return function ($context) {
$context->log('Amazing Function Log');
return $context->res->json([
'APPWRITE_FUNCTION_ID' => \getenv('APPWRITE_FUNCTION_ID') ?: '',
'APPWRITE_FUNCTION_NAME' => \getenv('APPWRITE_FUNCTION_NAME') ?: '',
'APPWRITE_FUNCTION_DEPLOYMENT' => \getenv('APPWRITE_FUNCTION_DEPLOYMENT') ?: '',
'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '',
'APPWRITE_VERSION' => \getenv('APPWRITE_VERSION') ?: '',
'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '',
'APPWRITE_FUNCTION_EVENT' => $context->req->headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' => $context->req->bodyRaw ?? '',
'APPWRITE_FUNCTION_DATA' => $context->req->bodyRaw ?? '',
'APPWRITE_FUNCTION_USER_ID' => $context->req->headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $context->req->headers['x-appwrite-user-jwt'] ?? '',
'APPWRITE_FUNCTION_PROJECT_ID' => \getenv('APPWRITE_FUNCTION_PROJECT_ID') ?: '',
'CUSTOM_VARIABLE' => \getenv('CUSTOM_VARIABLE') ?: '',
]);
};
@@ -1,18 +0,0 @@
{
"name": "appwrite/cloud-function-demo",
"description": "Demo cloud function script",
"type": "library",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Team Appwrite",
"email": "team@appwrite.io"
}
],
"require": {
"php": ">=7.4.0",
"ext-curl": "*",
"ext-json": "*",
"appwrite/appwrite": "1.1.*"
}
}
-64
View File
@@ -1,64 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "afdff6a172e6c44aee11f1562175f81a",
"packages": [
{
"name": "appwrite/appwrite",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-for-php.git",
"reference": "98b327d3fd18a72f4582019916afd735a0e9e0e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/98b327d3fd18a72f4582019916afd735a0e9e0e7",
"reference": "98b327d3fd18a72f4582019916afd735a0e9e0e7",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"php": ">=7.1.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.35"
},
"type": "library",
"autoload": {
"psr-4": {
"Appwrite\\": "src/Appwrite"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks",
"support": {
"email": "team@localhost.test",
"issues": "https://github.com/appwrite/sdk-for-php/issues",
"source": "https://github.com/appwrite/sdk-for-php/tree/1.1.2",
"url": "https://appwrite.io/support"
},
"time": "2020-08-15T18:24:32+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.4.0",
"ext-curl": "*",
"ext-json": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}
@@ -1,16 +0,0 @@
<?php
return function ($context) {
return $context->res->json([
'APPWRITE_FUNCTION_ID' => \getenv('APPWRITE_FUNCTION_ID') ?: '',
'APPWRITE_FUNCTION_NAME' => \getenv('APPWRITE_FUNCTION_NAME') ?: '',
'APPWRITE_FUNCTION_DEPLOYMENT' => \getenv('APPWRITE_FUNCTION_DEPLOYMENT') ?: '',
'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '',
'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '',
'APPWRITE_FUNCTION_CPUS' => \getenv('APPWRITE_FUNCTION_CPUS') ?: '',
'APPWRITE_FUNCTION_MEMORY' => \getenv('APPWRITE_FUNCTION_MEMORY') ?: '',
'UNICODE_TEST' => "êä"
]);
};
@@ -1,18 +0,0 @@
{
"name": "appwrite/php-scopes",
"description": "PHP scopes test script",
"type": "library",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Team Appwrite",
"email": "team@appwrite.io"
}
],
"require": {
"php": ">=7.4.0",
"ext-curl": "*",
"ext-json": "*",
"appwrite/appwrite": "11.0.*"
}
}
@@ -1,18 +0,0 @@
<?php
require 'vendor/autoload.php';
use Appwrite\Client;
use Appwrite\Services\Users;
return function ($context) {
$client = new Client();
$client
->setEndpoint(getenv('APPWRITE_FUNCTION_API_ENDPOINT'))
->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID'))
->setKey($context->req->headers['x-appwrite-key']);
$users = new Users($client);
$response = $users->list();
$context->log($response);
return $context->res->json($response);
};
@@ -1,7 +0,0 @@
<?php
return function ($req, $res) {
$res->json([
'v2Woks' => true
]);
};
-39
View File
@@ -1,39 +0,0 @@
<?php
return function ($context) {
if ($context->req->path === '/custom-response') {
$code = (int) ($context->req->query['code'] ?? '200');
$body = $context->req->query['body'] ?? '';
return $context->res->send($body, $code);
}
$context->log('body-is-' . ($context->req->body ?? ''));
$context->log('custom-header-is-' . ($context->req->headers['x-custom-header'] ?? ''));
$context->log('method-is-' . \strtolower($context->req->method ?? ''));
$context->log('path-is-' . ($context->req->path ?? ''));
$context->log('user-is-' . $context->req->headers['x-appwrite-user-id'] ?? '');
if (empty($context->req->headers['x-appwrite-user-jwt'] ?? '')) {
$context->log('jwt-is-invalid');
} else {
$context->log('jwt-is-valid');
}
$context->error('error-log-works');
$statusCode = $context->req->query['code'] ?? '200';
return $context->res->json([
'APPWRITE_FUNCTION_ID' => \getenv('APPWRITE_FUNCTION_ID') ?: '',
'APPWRITE_FUNCTION_NAME' => \getenv('APPWRITE_FUNCTION_NAME') ?: '',
'APPWRITE_FUNCTION_DEPLOYMENT' => \getenv('APPWRITE_FUNCTION_DEPLOYMENT') ?: '',
'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '',
'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '',
'UNICODE_TEST' => "êä",
'GLOBAL_VARIABLE' => \getenv('GLOBAL_VARIABLE') ?: '',
'APPWRITE_FUNCTION_CPUS' => \getenv('APPWRITE_FUNCTION_CPUS') ?: '',
'APPWRITE_FUNCTION_MEMORY' => \getenv('APPWRITE_FUNCTION_MEMORY') ?: '',
], \intval($statusCode));
};

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