Compare commits

...
Author SHA1 Message Date
Matej Bačo 2889f00192 Use new console 2023-12-07 07:57:36 +01:00
Khushboo Verma 3436924c09 Removed empty properties from email template 2023-12-07 00:16:03 +01:00
Khushboo Verma 43d6515f04 Merge branch 'feat-1.5-auth' of github.com:appwrite/appwrite into feat-1.5-auth 2023-12-07 00:08:00 +01:00
Khushboo Verma c2c1163501 Fix endpoint 2023-12-07 00:07:40 +01:00
Matej Bačo 5709d0e457 Fix lengthy sessions 2023-12-06 23:57:58 +01:00
Matej Bačo f60e844ce3 Fix bugs 2023-12-06 23:56:48 +01:00
Khushboo Verma f7b3d4dfa8 Add notify to phone and oauth 2023-12-06 23:55:31 +01:00
Khushboo Verma 57035f35f8 Updated the arrays 2023-12-06 23:36:15 +01:00
Khushboo Verma 31cc6a4ca3 Updated array 2023-12-06 23:19:35 +01:00
Khushboo Verma 9c4d329ceb Fix the order 2023-12-06 23:00:05 +01:00
Khushboo Verma 998642a875 Merge branch 'feat-1.5-auth' of github.com:appwrite/appwrite into feat-1.5-auth 2023-12-06 22:38:59 +01:00
Khushboo Verma d7041c0efd WIP: Add security phrase to magic URL 2023-12-06 22:38:38 +01:00
Matej Bačo ea5de9bde3 Fix notify feature 2023-12-06 18:40:18 +01:00
Khushboo Verma 7abafbb09c Alert on new session creation 2023-12-06 17:35:09 +01:00
Khushboo Verma ea2302238d Alert on new session creation 2023-12-06 17:30:37 +01:00
Matej Bačo a2df1932cd Merge branch 'feat-1.5-auth' of https://github.com/appwrite/appwrite into feat-1.5-auth 2023-12-06 12:52:18 +01:00
Matej Bačo 5dab541d76 Auth features 2023-12-06 12:51:34 +01:00
Khushboo Verma 22b1002305 Add checks for 100k, 1m, 10m password dictionary 2023-12-06 12:02:38 +01:00
Khushboo Verma bb1357f178 Resolved merge conflicts 2023-12-05 14:09:20 +01:00
Khushboo Verma 14afed5eb2 Password dictionary WIP 2023-12-05 14:07:17 +01:00
Matej Bačo 50d6dcc745 Add auth features 2023-12-05 13:57:28 +01:00
Matej Bačo 6459abeea2 Add ai password 2023-12-05 12:58:36 +01:00
Khushboo Verma d822878fe6 Update projects endpoint 2023-12-05 12:57:26 +01:00
Khushboo VermaandGitHub 34d3e4fbfd Add files via upload 2023-12-05 11:59:42 +01:00
Matej BačoandGitHub a989b181fd Merge pull request #7239 from appwrite/feat-session-extension 2023-12-05 11:56:55 +01:00
Khushboo Verma c64798d1c3 Logout from all sessions except current 2023-12-05 11:47:39 +01:00
Matej Bačo d85cb572b0 Implement session extension 2023-12-04 17:13:14 +01:00
Jake BarnbyandGitHub 396ff87fbc Merge pull request #7219 from appwrite/feat-add-message-provider-type
Feat add message provider type
2023-11-29 23:05:46 +13:00
prateek banga 739cc36fdc updated providerType wherever left to update 2023-11-29 15:22:26 +05:30
prateek banga fa8dc133b4 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-add-message-provider-type 2023-11-29 15:01:47 +05:30
Jake BarnbyandGitHub 82874a1100 Merge pull request #7217 from appwrite/target-changes
adds target when creating user via server endpoint
2023-11-29 21:19:58 +13:00
prateek banga 43b8fc2c31 review changes 2023-11-29 13:47:44 +05:30
Jake Barnby 042c1a725f Message extends Model instead of Any so rules are applied since data is not hoisted 2023-11-29 17:10:53 +13:00
Jake Barnby 4b58d08fd8 Add providerType to Message 2023-11-29 17:09:44 +13:00
Jake Barnby 82c86c0ae2 deliveryTime -> scheduledAt 2023-11-29 17:08:25 +13:00
Jake Barnby 4bc23afc65 Add constants for message types 2023-11-29 17:05:37 +13:00
prateek banga ee4c2d0e0d adds target when creating user via server endpoint 2023-11-28 18:42:34 +05:30
Jake BarnbyandGitHub eb4083641f Merge pull request #7195 from appwrite/feat-optional-providers-creation
makes provider creation fields optional and adds target info in subsc…
2023-11-24 19:47:12 +13:00
prateek banga e3f60f4c78 fix tests 2023-11-23 23:14:02 +05:30
prateek banga 858175fe00 adds target object in subscriber model 2023-11-23 14:35:16 +05:30
Prateek Banga d9826cdce8 makes provider creation fields optional and adds target info in subscriber model 2023-11-23 00:39:55 +05:30
Jake BarnbyandGitHub 90c47ea241 Merge pull request #7188 from appwrite/refactor-textmagic-change
changes TextMagic to Textmagic in all places and uses email validator
2023-11-21 23:03:36 +13:00
prateek banga 9c6cd8512f review changes 2023-11-21 15:19:19 +05:30
prateek banga 88f228c106 review changes 2023-11-21 15:11:09 +05:30
prateek banga 3394218cb0 adds validation for phone and email identifier 2023-11-21 15:00:02 +05:30
prateek banga 520d19e335 changes TextMagic to Textmagic in all places and uses email validator 2023-11-21 12:35:36 +05:30
Jake Barnby 0b01f7140f Merge remote-tracking branch 'origin/main' into 1.5.x
# Conflicts:
#	app/config/errors.php
#	composer.lock
#	src/Appwrite/Extend/Exception.php
2023-11-21 00:19:17 +13:00
Jake BarnbyandGitHub 8caf61bcd1 Merge pull request #7177 from appwrite/feat-subscriber-username
adds user name in subscriber response model
2023-11-20 23:53:49 +13:00
prateek banga b1eaf97806 review changes 2023-11-20 16:21:06 +05:30
prateek banga 5c1cf3a526 adds user name in subscriber response model 2023-11-20 12:34:50 +05:30
Torsten DittmannandGitHub 5a715ff68c Merge pull request #7149 from appwrite/1.4.x
1.4.x
2023-11-16 20:42:41 +01:00
Christy JacobandGitHub 8013d79ae0 Merge pull request #7156 from appwrite/fix-orfaned-project-deleteion
comparison fix
2023-11-16 13:40:46 -05:00
shimon 8c9a662e14 comparison fix 2023-11-16 20:33:37 +02:00
shimon dceb1858b0 comparison fix 2023-11-16 20:31:53 +02:00
Jake BarnbyandGitHub 7eb6b05e05 Merge pull request #7152 from appwrite/feat-account-target
Feat account target
2023-11-17 00:54:54 +13:00
f0247930da Update app/config/collections.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2023-11-16 17:15:08 +05:30
prateek banga 7536d8eb0f review changes 2023-11-16 17:09:08 +05:30
prateek banga bf95736da0 review changes 2023-11-16 16:47:36 +05:30
prateek banga 14e8234889 Merge branch 'feat-account-target' of https://github.com/appwrite/appwrite into feat-account-target 2023-11-16 16:27:30 +05:30
prateek banga f854622705 review changes 2023-11-16 16:26:36 +05:30
Prateek BangaandGitHub 893b65ce8f Merge branch '1.5.x' into feat-account-target 2023-11-16 14:17:27 +05:30
Jake BarnbyandGitHub 23078c43e2 Merge pull request #7151 from appwrite/feat-remove-internal-provider
removes internal provider
2023-11-16 21:45:38 +13:00
prateek banga c1869bb0ca adds messaging in test workflow 2023-11-16 11:20:39 +05:30
Prateek BangaandGitHub 6a52dd6a20 Merge branch '1.5.x' into feat-remove-internal-provider 2023-11-16 11:03:45 +05:30
Prateek BangaandGitHub 6a3fa7d5c5 Merge branch '1.5.x' into feat-account-target 2023-11-16 11:03:38 +05:30
prateek banga a282ab38a6 adds target endpoint in account controller for push tokensl 2023-11-16 02:12:06 +05:30
prateek banga c728d9bc8e lint fix 2023-11-16 01:36:22 +05:30
prateek banga 549dcc493d lint fix 2023-11-16 01:33:05 +05:30
prateek banga c24664f5d9 removes internal provider 2023-11-16 01:30:47 +05:30
Christy JacobandGitHub 7cce033682 Merge branch 'main' into 1.4.x 2023-11-15 11:13:53 -08:00
Christy JacobandGitHub 8e911dc83e Merge pull request #7150 from appwrite/chore-update-version
chore: update versions and changelog
2023-11-15 11:02:01 -08:00
德华 刘 e145ad4cb4 chore: update versions and changelog 2023-11-15 18:41:57 +00:00
Christy JacobandGitHub 6826a9efe0 Merge pull request #7124 from appwrite/usage-auto-skip-fix
wrapping create stats query
2023-11-15 10:38:47 -08:00
德华 刘 37e1e25170 chore: update versions and changelog 2023-11-15 18:31:33 +00:00
Christy JacobandGitHub 90ea00398b Merge pull request #7138 from appwrite/chore-update-database
Chore update database
2023-11-15 10:28:24 -08:00
Christy JacobandGitHub 5a06c015da Merge pull request #7148 from appwrite/chore-bump-console-3.2.7
Bump console to version 3.2.7
2023-11-15 10:28:03 -08:00
Christy JacobandGitHub e36dc817fb Merge branch '1.4.x' into usage-auto-skip-fix 2023-11-15 10:27:44 -08:00
Christy JacobandGitHub bc09b50c11 Merge pull request #7095 from appwrite/feat-delete-orphaned-projects
Add commit flag to delete orphaned projects task
2023-11-15 10:12:00 -08:00
shimon 6b5e734dfa indentation small fix 2023-11-15 20:09:50 +02:00
Steven Nguyen 31d5b75034 Bump console to version 3.2.7 2023-11-15 17:23:35 +00:00
德华 刘 7efecbca8b chore: fixed indentation 2023-11-15 17:20:50 +00:00
Torsten DittmannandGitHub 7766279025 Merge pull request #7041 from krishvsoni/kvs-patch
Update README-CN.md
2023-11-15 16:19:20 +01:00
Torsten DittmannandGitHub 115ca23a58 Merge pull request #5174 from yatharth1706/fix-4797-verified-user-getting-email-verifications
Fix verified user getting email verifications
2023-11-15 13:35:23 +01:00
Jake BarnbyandGitHub 71b5e1194f Merge pull request #7132 from appwrite/feat-target-provider-type
Feat target provider type
2023-11-15 20:25:43 +13:00
prateek banga 2aa8391c5e review fixes 2023-11-15 12:52:27 +05:30
Jake BarnbyandGitHub 74604a1a60 Merge pull request #7140 from appwrite/fix-delete-installation
Fix git installation deletion
2023-11-15 19:39:36 +13:00
Khushboo Verma 2e70ed59af Fix git installation deletion 2023-11-15 11:41:42 +05:30
Jake Barnby db9a946b0c Merge remote-tracking branch 'origin/1.5.x' into feat-target-provider-type 2023-11-15 19:08:59 +13:00
Jake BarnbyandGitHub a3c40fcf95 Merge pull request #7129 from appwrite/more_review_changes
More review changes
2023-11-15 19:08:00 +13:00
Jake Barnby 94931ff4ce Fix non-PSR-compliant file name 2023-11-15 16:47:49 +13:00
Jake Barnby c3015340ce Update database 2023-11-15 16:46:43 +13:00
prateek banga fdca55b0de fix test cases 2023-11-15 01:24:55 +05:30
prateek banga adc76c5797 adds target when account is created or email or phone is updated 2023-11-14 22:46:20 +05:30
Eldad A. FuxandGitHub f6408cd945 Merge pull request #7134 from appwrite/fix-recreate-repositories-documents
Fix patch script, make errors silent
2023-11-14 12:06:01 -05:00
Matej Bačo 1c4fea0fc3 Fix patch script, make errors silent 2023-11-14 17:45:02 +01:00
Eldad A. FuxandGitHub 29cd164448 Merge pull request #7133 from appwrite/fix-repositories-collection
Repositories recreation script
2023-11-14 11:09:38 -05:00
prateek banga 1870524be5 fixes error name 2023-11-14 21:34:17 +05:30
Matej Bačo 7e1b618769 Fix permission issues with repositories collection 2023-11-14 16:50:59 +01:00
shimon c2e5849c49 removing blank line 2023-11-14 16:49:45 +02:00
Khushboo Verma 141b864a56 Update permissions for create repository document 2023-11-14 19:49:23 +05:30
Matej Bačo 2572cb43c2 Code cleanup 2023-11-14 15:11:17 +01:00
Matej Bačo c133bccfa9 Finish recreate repos docs script 2023-11-14 15:06:58 +01:00
Matej Bačo fb6455b783 Add script to patch missing repos documents 2023-11-14 14:43:33 +01:00
Matej Bačo 4f2f76db22 Improve deletion relation with IDs 2023-11-14 14:11:54 +01:00
prateek banga 7ae614fe13 adds provider type in target 2023-11-14 18:14:07 +05:30
Torsten DittmannandGitHub 89c097eed3 Merge pull request #7131 from appwrite/fix-repository-deletion-bug
Only delete repositories linked to the particular project
2023-11-14 13:25:28 +01:00
Khushboo Verma 78eb5105e7 Update projectInternalId var 2023-11-14 17:08:09 +05:30
Khushboo Verma be85b173e7 Only delete repositories linked to the particular project 2023-11-14 16:49:16 +05:30
prateek banga c5aaa670a9 adds provider details in message search attribute 2023-11-14 15:20:21 +05:30
prateek banga f84a00dded adds search attribute filter 2023-11-14 14:07:52 +05:30
prateek banga 8f53301209 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into more_review_changes 2023-11-14 12:26:35 +05:30
shimon 3d9ee8bc52 delete orphaned projects task 2023-11-13 19:28:48 +02:00
shimon 60c0f4c973 delete orphaned projects task 2023-11-13 19:27:20 +02:00
shimon dd0bf66212 delete orphaned projects task 2023-11-13 19:24:55 +02:00
Eldad A. FuxandGitHub be6b31b43f Merge pull request #7123 from appwrite/feat-health-tresholds
Implement health tresholds
2023-11-13 08:57:51 -05:00
shimon b9007a5956 wrapping create stats query
with Authorization::skip
2023-11-13 15:41:10 +02:00
Matej Bačo 967854d34a Fix grammar 2023-11-13 14:39:33 +01:00
Matej Bačo a029287e5a Implement health tresholds 2023-11-13 14:07:11 +01:00
Jake BarnbyandGitHub e2aa06ed84 Merge pull request #7120 from appwrite/chore-sync-main
Chore sync main
2023-11-13 17:09:45 +13:00
Jake Barnby 458c2d1446 Merge remote-tracking branch 'origin/main' into chore-sync-main 2023-11-13 16:46:05 +13:00
Jake BarnbyandGitHub 5871178fc0 Merge pull request #7106 from appwrite/1.4.x
1.4.x
2023-11-13 16:45:23 +13:00
Jake BarnbyandGitHub 162fac5724 Merge pull request #7115 from appwrite/chore-sync-main-1.5.x
Chore sync main 1.5.x
2023-11-13 13:43:18 +13:00
Prateek BangaandGitHub 4f681cfe10 Merge branch '1.5.x' into chore-sync-main-1.5.x 2023-11-10 23:12:40 +05:30
Torsten DittmannandGitHub 4c084afa1e Merge pull request #7069 from appwrite/feat-logs-messaging-api
Feat logs messaging api
2023-11-10 14:24:15 +01:00
Prateek Banga 1b99f47f21 lint fix 2023-11-10 13:28:39 +05:30
Prateek Banga 3ade88897c Merge branch 'main' of https://github.com/appwrite/appwrite into chore-sync-main-1.5.x 2023-11-10 12:49:05 +05:30
Prateek Banga 3b5379a0cb added more assertions in logs test and moved them to console 2023-11-10 12:31:21 +05:30
Jake BarnbyandGitHub 573a035f92 Merge pull request #7112 from appwrite/chore-update-version
Update version + cache buster
2023-11-10 13:46:11 +13:00
Jake Barnby 97757c9984 Update readmes + migrations 2023-11-10 13:25:28 +13:00
Jake Barnby 4d54e7bb36 Update version + cache buster 2023-11-10 13:09:00 +13:00
Jake BarnbyandGitHub 2d9ac9bac1 Merge pull request #7111 from appwrite/chore-update-db
Chore update db
2023-11-10 12:42:53 +13:00
Jake Barnby 17919ecfbe Update database 2023-11-10 12:11:21 +13:00
Prateek Banga 76ed15f2a1 adds more assertion in logs tests 2023-11-10 02:20:55 +05:30
Christy JacobandGitHub 7e8778c185 Merge pull request #7104 from appwrite/chore-bump-dependencies
Chore bump dependencies
2023-11-09 08:20:36 -08:00
德华 刘 f1ea4f761e chore: fix markdown syntax 2023-11-09 16:08:20 +00:00
德华 刘 c014e40c54 chore: update changelog 2023-11-09 16:05:33 +00:00
德华 刘 f70b418c5c chore: updates for appwrite 1.4.10 2023-11-09 15:57:37 +00:00
Christy JacobandGitHub e2fa5356bc Merge branch '1.4.x' into chore-bump-dependencies 2023-11-09 07:55:12 -08:00
Christy JacobandGitHub acc49eb998 Merge pull request #7092 from appwrite/fix-null-password-history
Handle cases where password history could contain NULLs
2023-11-09 07:53:02 -08:00
Christy JacobandGitHub d33b8c2ce1 Merge branch '1.4.x' into fix-null-password-history 2023-11-09 07:44:44 -08:00
Bradley SchofieldandGitHub 56310bc4fa Merge branch 'main' into fix-null-password-history 2023-11-09 10:41:17 +00:00
Prateek Banga 4106dd5bbd Merge branch 'feat-logs-messaging-api' of https://github.com/appwrite/appwrite into feat-logs-messaging-api 2023-11-09 14:53:53 +05:30
Prateek Banga 3654799cc7 links ISO 8601, move params in messaging controllers 2023-11-09 14:53:42 +05:30
Prateek BangaandGitHub 4c970cbc46 Merge branch '1.5.x' into feat-logs-messaging-api 2023-11-09 13:42:51 +05:30
Jake BarnbyandGitHub 2365626270 Merge pull request #7058 from appwrite/event-label-messaging
Event label messaging
2023-11-09 21:08:36 +13:00
Prateek Banga bd39c5ed2e Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-logs-messaging-api 2023-11-09 12:40:05 +05:30
Prateek Banga 8d5bfdabc6 revert wrong aduits.event label 2023-11-09 12:29:16 +05:30
Jake Barnby 1487897583 Merge remote-tracking branch 'origin/1.5.x' into event-label-messaging 2023-11-09 19:49:08 +13:00
Jake BarnbyandGitHub a9c7b640ec Merge pull request #7048 from appwrite/1.5.x_review_changes
review changes
2023-11-09 19:47:09 +13:00
Steven Nguyen 5087ae61e7 Bump utopia-php/vcs and utopia-php/messaging 2023-11-09 05:51:47 +00:00
Steven NguyenandGitHub 0c264a107a Merge branch '1.4.x' into fix-4797-verified-user-getting-email-verifications 2023-11-08 17:37:43 -08:00
Christy JacobandGitHub fcf9e9c4d0 Merge pull request #7066 from appwrite/fix-vcs-documents-not-deleted
Delete linked VCS repos and comments
2023-11-08 14:28:45 -08:00
德华 刘 2bf247053d chore: address review comments 2023-11-08 21:59:29 +00:00
Eldad A. FuxandGitHub d64d56069f Merge pull request #6828 from appwrite/feat-improve-migration-validations
Improve Error Handling and Add more Validation
2023-11-08 16:03:04 -05:00
prateek banga b9de4a0169 added tests for logs endpoint 2023-11-08 23:26:46 +05:30
Christy JacobandGitHub 780dffd973 Merge pull request #7056 from appwrite/fix-prevent-webhook-abuse
Added cURL timeout and not store response data for webhooks
2023-11-08 07:35:38 -08:00
Eldad A. FuxandGitHub 2baadb6e95 Merge pull request #7096 from appwrite/refactor-db-health
Refactor db health api
2023-11-08 09:54:10 -05:00
Bradley Schofield 54e5985940 Fix wrong operator for password history
This fixes a potential chance for nulls to be introduced into the password history array when $password is null and $passwordHistory != 0
2023-11-08 14:35:16 +00:00
shimon dd9d8010c1 db health api throws an error when ping fails 2023-11-08 15:20:50 +02:00
shimon bae9da5695 Merge remote-tracking branch 'origin/main' 2023-11-08 15:13:53 +02:00
Khushboo Verma 05ab73ee37 Update download limit to 5 MB 2023-11-08 17:12:07 +05:30
Khushboo Verma e461a4f14b Added max download limit of 1 GB 2023-11-08 16:09:17 +05:30
shimon 0f3c438976 added inf metric 2023-11-08 12:05:51 +02:00
Khushboo Verma 365c520267 Fixed linter error 2023-11-08 12:41:16 +05:30
Khushboo Verma 698bce2c36 Limit max redirects to 5 2023-11-08 12:32:48 +05:30
Khushboo Verma 93d3cb7bcf Merge branch '1.4.x' into fix-prevent-webhook-abuse 2023-11-08 11:30:00 +05:30
Christy JacobandGitHub fd7b354113 Merge pull request #7084 from appwrite/fix-proxy-create-rule-500
fix: proxy create rule 500
2023-11-07 10:49:52 -08:00
Christy JacobandGitHub 8feb01d857 Merge pull request #7085 from appwrite/fix-missing-sessionId-error
fix: missing sessionId error
2023-11-07 10:43:05 -08:00
Christy JacobandGitHub 888c7bf39c Merge pull request #7091 from appwrite/fix-missing-functionId-error
fix: missing functionId error on create execution
2023-11-07 10:41:55 -08:00
Christy JacobandGitHub 2d2400fdff Merge branch '1.4.x' into fix-missing-sessionId-error 2023-11-07 10:27:46 -08:00
Christy JacobandGitHub c079d2f978 Merge branch '1.4.x' into fix-missing-functionId-error 2023-11-07 10:27:37 -08:00
Christy JacobandGitHub f4cdd0accc Merge branch '1.4.x' into fix-proxy-create-rule-500 2023-11-07 10:27:31 -08:00
Christy JacobandGitHub 8dff9b9b49 Merge pull request #7093 from appwrite/sync-1.4.x-main
Sync 1.4.x main
2023-11-07 10:26:04 -08:00
德华 刘 9acc23049c Merge branch 'main' of https://github.com/appwrite/appwrite into sync-1.4.x-main 2023-11-07 18:12:58 +00:00
Christy JacobandGitHub f53fbc19d4 Merge branch '1.4.x' into fix-missing-functionId-error 2023-11-07 10:10:03 -08:00
Bradley Schofield d5d7440a68 Merge branch 'main' into feat-improve-migration-validations 2023-11-07 16:21:35 +00:00
Bradley Schofield 44cbfb2769 Add null check to hash 2023-11-07 16:17:14 +00:00
Christy JacobandGitHub cb512710f2 Merge pull request #7087 from appwrite/fix-4960-disabled-usage
Ensure usage endpoints don't throw 500 when usage is disabled
2023-11-07 07:34:06 -08:00
Khushboo Verma e4191f206d Merge branch '1.4.x' into fix-prevent-webhook-abuse 2023-11-07 17:34:10 +05:30
loks0n 7e37c973ce fix: missing functionId error on create execution 2023-11-07 10:25:33 +00:00
Eldad A. FuxandGitHub 8db73a3431 Merge pull request #7078 from appwrite/fix-optional-services
Fix optional services
2023-11-07 05:14:52 -05:00
Khushboo Verma 018800c7ae Merge branch '1.4.x' into fix-prevent-webhook-abuse 2023-11-07 13:02:25 +05:30
Steven Nguyen 55a73efeab Ensure usage endpoints don't throw 500 when usage is disabled 2023-11-06 21:28:45 +00:00
loks0n dfc8c919d4 fix: missing sessionId error 2023-11-06 15:06:56 +00:00
loks0n 75669b9b2e chore: better error msg 2023-11-06 14:49:33 +00:00
loks0n 4440859c05 chore: fmt 2023-11-06 14:48:54 +00:00
loks0n 61c5f371b7 chore: use base Exception 2023-11-06 14:45:56 +00:00
loks0n 8b24c44fb9 fix: proxy create rule 500 2023-11-06 14:44:00 +00:00
Jake Barnby 2292143750 Add service/all test 2023-11-06 12:34:44 +13:00
Jake Barnby 1429c82aa0 Make VCS, proxy and migrations services non-optional so they aren't toggled with enable/disable all service routes 2023-11-06 12:04:56 +13:00
KRISH SONIandGitHub e4bde92b35 Merge branch 'main' into kvs-patch 2023-11-05 20:38:36 +05:30
Yatharth Verma 0ecdeed329 Checkout to latest tag of console submodule 2023-11-04 13:01:05 +05:30
Christy JacobandGitHub 19e5235491 Merge pull request #7073 from appwrite/fix-health-queue-size
Fix the Health service's get X queue endpoints
2023-11-04 00:14:54 +04:00
Christy JacobandGitHub bcdac2f803 Merge pull request #7074 from appwrite/fix-delete-schedules
Fix regression from worker refactor
2023-11-04 00:11:56 +04:00
Steven Nguyen 30b8ff9166 Add pagination to listByGroup to fix infinite loop problem 2023-11-03 17:47:52 +00:00
Steven Nguyen 03da0380ea Ensure schedules for deleted projects are deleted 2023-11-03 17:47:16 +00:00
Steven Nguyen 9f7bbe4869 Fix the Health service's get X queue endpoints
Before this, the endpoints called $client->sumProcessingJobs() which
only gets the currently processing jobs, but this endpoint should return
what's currently in queue, pending to be processed. This should be
retrieved using $client->getQueueSize().
2023-11-03 16:57:30 +00:00
Christy JacobandGitHub 9fc7c1384a Merge pull request #7071 from appwrite/fix-swoole-warning
feat: fix swoole warning
2023-11-03 19:46:59 +04:00
loks0n 305c781206 feat: fix swoole warning 2023-11-03 12:18:24 +00:00
Khushboo Verma 4a3b8fb70e Change projectId to projectInternalId 2023-11-03 15:32:50 +05:30
Khushboo Verma 9be32184d6 Refactor loop 2023-11-03 14:54:51 +05:30
Christy JacobandGitHub 8e1afa11f9 Merge pull request #7070 from appwrite/feat-remove-mock-endpoints
Feat remove mock endpoints
2023-11-02 21:38:15 +04:00
Bradley Schofield 23631f8282 Update specs 2023-11-02 17:12:08 +00:00
Bradley Schofield d921c03ee1 Update mock.php 2023-11-02 17:07:16 +00:00
Bradley Schofield b15dac16c8 Revert "Remove Mock API"
This reverts commit 2d045335ff.
2023-11-02 17:05:51 +00:00
Bradley Schofield 2d045335ff Remove Mock API 2023-11-02 17:05:31 +00:00
Prateek Banga f97d1fa9e3 adds log api in messaging controller 2023-11-02 18:00:17 +05:30
Prateek Banga 61d46bee5c Merge branch '1.5.x_review_changes' of https://github.com/appwrite/appwrite into event-label-messaging 2023-11-02 16:53:55 +05:30
Prateek Banga 270e6e7c15 review changes 2023-11-02 16:43:24 +05:30
Bradley Schofield e158d134b0 Merge branch 'main' into feat-improve-migration-validations 2023-11-02 10:48:38 +00:00
Prateek Banga 48744f66c8 Merge branch '1.5.x_review_changes' of https://github.com/appwrite/appwrite into event-label-messaging 2023-11-02 12:21:17 +05:30
Prateek Banga 76d7369314 fixes description for messaging service models 2023-11-02 12:20:15 +05:30
KRISH SONIandGitHub 49e23e8916 Merge branch 'main' into kvs-patch 2023-11-01 21:05:14 +05:30
Khushboo Verma d95e4790a0 Delete VCS documents when project is deleted 2023-11-01 15:06:28 +05:30
Khushboo Verma 8798d183bd Merge branch '1.4.x' into fix-vcs-documents-not-deleted 2023-11-01 14:41:07 +05:30
Khushboo Verma 64630490a5 Delete linked VCS repos and comments on function deletion 2023-11-01 14:36:31 +05:30
prateek banga fda4e6a8bd fixes typo in event label 2023-11-01 14:33:13 +05:30
Christy JacobandGitHub 281b50ba4a Merge pull request #7063 from appwrite/1.4.x
1.4.x
2023-11-01 12:32:11 +04:00
Jake Barnby 3652ab61d8 Revert "Enum tests"
This reverts commit 74450b1178.
2023-11-01 21:02:42 +13:00
Prateek Banga 2aa5c5742b remove unnecessary injection 2023-11-01 12:09:00 +05:30
Jake Barnby 74450b1178 Enum tests 2023-11-01 18:29:54 +13:00
Christy JacobandGitHub 7110836beb Merge pull request #7061 from appwrite/feat-1.4.9-release-prep
Prepare 1.4.9 release
2023-10-31 23:59:11 +04:00
Steven Nguyen 358fdb6878 Add 1.4.9 to CHANGES.md 2023-10-31 12:43:52 -07:00
Steven Nguyen 755699de8a Bump appwrite version to 1.4.9 2023-10-31 12:40:45 -07:00
Christy JacobandGitHub 311fc0f0c4 Merge pull request #7059 from appwrite/fix/function-domains-execution
Fix 400 error on function domain execution
2023-10-31 23:17:16 +04:00
Steven NguyenandGitHub 7c8a9ad4d2 Fix incorrect general_protocol_unsupported error
$requestHeaders was being referenced before it was set. This PR reverts
code back to using $swooleRequest to extract the header.
2023-10-31 18:31:36 +00:00
Steven NguyenandGitHub c4e22b1085 Fix warning when cookie is null 2023-10-31 18:25:35 +00:00
prateek banga f7529140bd adds event label to remaining messaging api 2023-10-31 23:53:46 +05:30
Prateek Banga a719c3f46b adds event label to create providers endpoint 2023-10-31 23:17:58 +05:30
Khushboo Verma ce6d873cf4 Added cURL timeout and not store response data for webhooks 2023-10-31 15:55:11 +05:30
KRISH SONIandGitHub 1809140659 Merge branch 'main' into kvs-patch 2023-10-31 15:12:48 +05:30
Prateek Banga a58f473b29 adds createdAt and updatedAt in messaging models 2023-10-31 14:01:42 +05:30
Christy JacobandGitHub 9fded16d3f Merge pull request #7053 from appwrite/1.4.x
Sync main with 1.4.x
2023-10-31 12:27:08 +04:00
prateek banga a4c8d852a5 adds test skip condition if dsn not provided 2023-10-31 02:34:18 +05:30
prateek banga 0dafc59cc2 converted env vars to dsn format 2023-10-31 02:05:50 +05:30
prateek banga a01d361886 review changes 2023-10-31 01:12:31 +05:30
prateek banga 06daa544e8 review changes 2023-10-30 23:37:57 +05:30
KRISH SONIandGitHub 42288f3070 Merge branch 'main' into kvs-patch 2023-10-30 21:08:42 +05:30
Christy JacobandGitHub 0a6ee966a2 Merge pull request #7045 from appwrite/update-project-creation-logic
chore: update project creation database allocation
2023-10-30 19:34:21 +04:00
德华 刘 744f2fbc1c chore: update project creation database allocation 2023-10-30 14:46:54 +00:00
KRISH SONIandGitHub e8110953b5 Merge branch 'main' into kvs-patch 2023-10-29 23:22:21 +05:30
KRISH SONIandGitHub 68486d4795 Update README-CN.md 2023-10-29 23:17:27 +05:30
Christy JacobandGitHub fbb82ceb81 Merge pull request #7028 from appwrite/feat-1.4.8-release-prep
Prepare 1.4.8 release
2023-10-27 23:32:00 +04:00
Steven Nguyen 4eab2a01f7 Add 1.4.8 to CHANGES.md 2023-10-27 10:31:43 -07:00
Steven Nguyen c1df289df7 Bump appwrite version to 1.4.8 2023-10-27 10:31:43 -07:00
Christy JacobandGitHub 255659f009 Merge pull request #7027 from appwrite/chore-bump-console
Bump console to version 3.2.5
2023-10-27 21:01:29 +04:00
Steven Nguyen c3b7b5af80 Fix inconsistent composer.lock
For some reason the composer.json had version 0.2.* of
utopia-php/messaging, but the composer.lock had 0.1.1.
2023-10-27 09:45:07 -07:00
Steven Nguyen 3abb74c937 Bump console to version 3.2.5 2023-10-27 09:33:21 -07:00
Christy JacobandGitHub 24824e1582 Merge pull request #7015 from appwrite/feat-delete-orphaned-projects
Feat delete orphaned projects
2023-10-27 20:21:52 +04:00
Christy JacobandGitHub 106f4cfc45 Merge pull request #6487 from appwrite/fix-unique-in-errors
Updated error message to not user magic strings
2023-10-27 20:13:16 +04:00
shimon 75484fa018 $collectionsConfig fix 2023-10-27 19:07:27 +03:00
shimon 917a4e9ab1 $collectionsConfig fix 2023-10-27 19:06:16 +03:00
Christy JacobandGitHub bcdebee598 Merge pull request #7024 from appwrite/fix-cookie-header
Fix: Cookie Header
2023-10-27 19:36:05 +04:00
Matej Bačo 9a6d5f8101 Improve var name 2023-10-27 17:25:19 +02:00
Matej Bačo 71807635b3 Move cookie-header logic to request 2023-10-27 17:23:43 +02:00
shimon 07ef5535ef dry run 2023-10-27 18:23:41 +03:00
Christy JacobandGitHub e17bb94e39 Merge pull request #6495 from appwrite/fix-mails
Fix certificate emails and add support for variables in email template subject
2023-10-27 19:17:50 +04:00
Christy JacobandGitHub 21220a2051 Merge pull request #6894 from appwrite/refactor-vcs-tests
Add VCS tests
2023-10-27 19:04:44 +04:00
德华 刘 6d237853e6 chore: revert console 2023-10-27 14:38:09 +00:00
Khushboo Verma 9164ed1045 Fixed linter errors 2023-10-27 19:43:07 +05:30
Khushboo Verma 30abfb01b7 Composer update 2023-10-27 19:40:24 +05:30
Khushboo Verma cba20d8b96 Addressed PR comments 2023-10-27 19:38:33 +05:30
Matej Bačo b2d385944a Add function domain cookie test 2023-10-27 15:33:26 +02:00
Khushboo Verma bb7278dd12 Merge branch 'refactor-vcs-tests' of github.com:appwrite/appwrite into refactor-vcs-tests 2023-10-27 18:14:47 +05:30
Khushboo Verma 36fcc6c293 Resolve merge conflicts 2023-10-27 18:13:50 +05:30
Matej Bačo 0a1a2af218 Connect VCS base to VCS test 2023-10-27 12:57:26 +02:00
Matej Bačo 84559b8f56 Improve cookie test 2023-10-27 12:01:37 +02:00
Christy JacobandGitHub 3700751592 Merge pull request #7016 from appwrite/main
Sync 1.4.x with main
2023-10-27 13:35:54 +04:00
德华 刘 2ee505b3f8 chore: update composer 2023-10-27 09:35:36 +00:00
Jake BarnbyandGitHub 26ecad1a6f Merge pull request #7014 from appwrite/feat-remove-provider-from-topics
removes provider from topics
2023-10-27 22:34:21 +13:00
Matej Bačo 740d9def8e Linter fix 2023-10-27 10:33:08 +02:00
Matej Bačo 94ff3baa9c Fix cookie headers 2023-10-27 10:26:46 +02:00
Prateek Banga bc6df4bab7 add recipients array check in API instead of worker 2023-10-27 13:42:27 +05:30
Steven Nguyen 7c1ab91ebc Add support for variables in email template subject 2023-10-26 16:58:31 -07:00
Steven Nguyen fc4fa977a0 Update emails from certificate worker to match pattern of other emails
Since the certificate worker is sending a job to the mails worker just
like the controllers, it should prepare the email in the same way by
localizing and then sending the inner template for the mail worker to
apply the layout and final variables.

This commit also fixes a problem with a missing subject.
2023-10-26 16:56:44 -07:00
Jake BarnbyandGitHub cb03ad1a15 Merge pull request #7003 from appwrite/feat-query-metadata
Feat query metadata
2023-10-27 12:52:34 +13:00
Steven Nguyen 836ce852ed Refactor email templates
Update the mails worker to only be responsible for building the final
email using the base (layout) template and the user, team, project, and
redirect variables.

It is the responsibility of the controller to perform localization and
determine whether a custom or default email template should be used.
If the default one is used, it has to be rendered using the hello,
footer, thanks, and signature variables so that it matches the custom
template. Then, it can be sent to the worker.
2023-10-26 16:47:14 -07:00
Steven Nguyen 3206cee36f Leave user placeholder in default email template
This matches up with the default template and gives the developer an
idea about the available variables and how to use them.
2023-10-26 16:23:58 -07:00
Steven Nguyen 70a3820009 Remove unused mail vars 2023-10-26 16:23:58 -07:00
Christy JacobandGitHub ab760ba46b Merge pull request #7017 from appwrite/chore-remove-install-analytics
Remove install analytics
2023-10-27 01:09:51 +04:00
Christy JacobandGitHub 3caa49a547 Merge pull request #6938 from appwrite/fix-gitpod-composer-install
gitpod: fix composer usage
2023-10-27 00:39:32 +04:00
Torsten Dittmann c81b4576b4 chore: remove install analytics 2023-10-26 22:25:35 +02:00
Christy JacobandGitHub 4becf5138d Merge pull request #7011 from krishvsoni/kvs-patch
added docs url
2023-10-27 00:25:17 +04:00
Christy JacobandGitHub 16fe42bca9 Merge pull request #6980 from appwrite/feat-support-selfdb
Fix database override
2023-10-27 00:21:59 +04:00
Christy JacobandGitHub d16e3f2f56 Merge branch '1.4.x' into feat-support-selfdb 2023-10-27 00:19:02 +04:00
shimon 45a0954b24 dry run 2023-10-26 21:12:43 +03:00
shimon fc348b2055 dry run 2023-10-26 21:12:13 +03:00
shimon 7e6378b23b dry run 2023-10-26 20:56:32 +03:00
shimon 0f43aa6872 dry run 2023-10-26 20:53:36 +03:00
Christy JacobandGitHub 2b939a57d0 Merge pull request #6198 from appwrite/chhore-parallelize-tests
chore: test parallel executions
2023-10-26 21:28:43 +04:00
德华 刘 c8788e2a13 chore: update CI 2023-10-26 16:43:50 +00:00
德华 刘 09912592cf chore: update CI 2023-10-26 16:27:57 +00:00
德华 刘 c9c688c592 chore: update CI 2023-10-26 16:26:13 +00:00
德华 刘 5d634384c8 chore: update CI 2023-10-26 16:24:14 +00:00
德华 刘 a4c59b838a chore: remove commented files 2023-10-26 16:15:52 +00:00
Matej Bačo c3a80dcebc Fix GraphQL function test 2023-10-26 16:50:35 +02:00
shimon 831122679f creating script 2023-10-26 17:45:23 +03:00
Prateek Banga 5a9b1f0c24 removes provider from topics 2023-10-26 19:44:06 +05:30
Christy JacobandGitHub 1d9b94ee4d Merge branch 'main' into chhore-parallelize-tests 2023-10-26 14:31:21 +04:00
Jake BarnbyandGitHub 8628e94d0c Merge pull request #7013 from appwrite/chore-updates-deps
Update database to include listCollections fix
2023-10-26 22:49:55 +13:00
Jake Barnby 89236955fb Fix setting metadata 2023-10-26 22:28:23 +13:00
Jake BarnbyandGitHub 92062a5463 Merge pull request #7010 from appwrite/review-changes-1.5.x
made review changes
2023-10-26 21:45:39 +13:00
Jake Barnby 995c404f35 Update db 2023-10-26 21:24:30 +13:00
Jake Barnby 2d897889c9 Merge remote-tracking branch 'origin/main' into chore-updates-deps 2023-10-26 21:22:41 +13:00
prateek banga c2cd544948 review changes 2023-10-26 13:46:45 +05:30
Prateek Banga 34c96e4672 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into review-changes-1.5.x 2023-10-26 12:37:51 +05:30
Prateek Banga 34b3ed68ee adds total count for subscribers in topics model 2023-10-26 12:37:39 +05:30
Jake BarnbyandGitHub ea76bc6680 Merge pull request #6991 from appwrite/providers-from-attribute
Providers from attribute
2023-10-26 19:52:02 +13:00
Prateek BangaandGitHub 9545e4ac72 Merge branch '1.5.x' into providers-from-attribute 2023-10-26 02:11:12 +05:30
prateek banga 456654c165 Merge branch 'providers-from-attribute' of https://github.com/appwrite/appwrite into review-changes-1.5.x 2023-10-26 00:03:13 +05:30
prateek banga f5aa226b57 fix test 2023-10-26 00:02:22 +05:30
KRISH SONIandGitHub 822596b2c9 Merge branch 'main' into kvs-patch 2023-10-25 23:56:54 +05:30
prateek banga f6f4397aab Merge branch 'providers-from-attribute' of https://github.com/appwrite/appwrite into review-changes-1.5.x 2023-10-25 23:55:45 +05:30
KRISH SONIandGitHub 191235ecaf added docs url 2023-10-25 23:24:26 +05:30
Torsten DittmannandGitHub 2910e87c7a Merge pull request #7009 from appwrite/fix-worker-permissions-reset
fix: worker database authorization reset
2023-10-25 19:51:20 +02:00
prateek banga 887eeef585 review changes 2023-10-25 23:09:38 +05:30
prateek banga fd4a81c4fc made review changes 2023-10-25 23:03:23 +05:30
Torsten Dittmann 404ad8653d fix: worker permissions reset 2023-10-25 19:23:55 +02:00
prateek banga 2435f722ca fixes test cases 2023-10-25 21:45:55 +05:30
Christy JacobandGitHub fa0d9139c1 Merge pull request #7007 from appwrite/chore-bump-console
chore: bump console version
2023-10-25 15:36:34 +04:00
Torsten Dittmann f9a3ae1421 chore: bump console version 2023-10-25 13:35:14 +02:00
Christy JacobandGitHub db52e047c6 Merge pull request #7006 from appwrite/fix-http-status-code
Updated the error code for unsupported protocol
2023-10-25 14:56:45 +04:00
Khushboo Verma 17de0d1ea7 Updated the error code for unsupported protocol 2023-10-25 15:55:46 +05:30
Jake Barnby 5732171a2a Merge remote-tracking branch 'origin/1.4.x' into feat-query-metadata
# Conflicts:
#	app/init.php
#	composer.json
#	composer.lock
2023-10-25 18:21:16 +13:00
Jake BarnbyandGitHub 7d10174193 Merge pull request #7002 from appwrite/chore-updates-deps
Update dependencies
2023-10-25 14:32:08 +13:00
Jake Barnby 8db372a7d3 Update dependencies 2023-10-25 13:55:32 +13:00
Torsten DittmannandGitHub 5069aef6ac Merge pull request #6998 from appwrite/feat-realtime-error-handling
feat: realtime error handling
2023-10-24 16:12:21 +02:00
Torsten Dittmann fbcbd0d430 feat: realtime error handling 2023-10-24 14:32:22 +02:00
Jake BarnbyandGitHub cbae797f38 Merge pull request #6965 from appwrite/sync-1.4.x-to-1.5.x
Sync 1.4.x to 1.5.x
2023-10-24 20:12:11 +13:00
Jake Barnby d5b6421285 Merge remote-tracking branch 'origin/1.4.x' into sync-1.4.x-to-1.5.x
# Conflicts:
#	composer.lock
2023-10-24 19:46:57 +13:00
Christy JacobandGitHub 3a53dce467 Merge pull request #6994 from appwrite/1.4.x
Update main with 1.4.x
2023-10-24 01:11:13 +04:00
Christy JacobandGitHub 3e2b6c4344 Merge pull request #6990 from appwrite/feat-1.4.7-release-prep
Prepare 1.4.7 release
2023-10-24 00:49:44 +04:00
Steven Nguyen 84ea81f56d Add 1.4.7 to CHANGES.md 2023-10-23 13:36:29 -07:00
Steven Nguyen 3addf76a2a Bump appwrite version to 1.4.7 2023-10-23 13:36:29 -07:00
Christy JacobandGitHub ce8c7e1527 Merge pull request #6988 from appwrite/fix-functions-worker
Fix missing body in async function execution
2023-10-23 23:40:58 +04:00
prateek banga b60cbcada5 adds from field in remaining providers 2023-10-24 01:05:46 +05:30
Torsten DittmannandGitHub 342deb4989 Merge pull request #6465 from appwrite/fix-docs-links
Fix docs links CONTRIBUTING.md
2023-10-23 19:24:43 +02:00
shimon 0411f9f530 fix function-worker body param typo 2023-10-23 16:33:26 +03:00
Khushboo Verma ad4db114bc Updated composer.lock 2023-10-23 15:23:52 +05:30
Khushboo Verma c0460289b7 Remove unused value from docker-compose 2023-10-23 15:19:23 +05:30
Khushboo Verma a09c4281c8 Revert "Remove unused value from docker-compose"
This reverts commit c7435e7e78.
2023-10-23 15:18:48 +05:30
Khushboo Verma c7435e7e78 Remove unused value from docker-compose 2023-10-23 15:16:42 +05:30
Khushboo Verma 2cf8639b68 Resolve merge conflicts 2023-10-23 15:06:01 +05:30
Khushboo Verma 97c1099961 Refactor code and add more assertions 2023-10-23 15:03:24 +05:30
shimon fc34d2ff7e Merge remote-tracking branch 'origin/main' 2023-10-23 11:47:30 +03:00
Christy JacobandGitHub 45c44bb51c Merge pull request #6986 from appwrite/fix-db-mod
fix mod condition
2023-10-23 12:41:05 +04:00
Christy JacobandGitHub 14f35589b3 Merge branch 'main' into fix-db-mod 2023-10-23 12:40:05 +04:00
德华 刘 3dee8d6ecd fix: update incorrect method call 2023-10-23 08:34:57 +00:00
808f682fa7 Update app/controllers/api/projects.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2023-10-23 11:25:31 +03:00
shimon 2ef8490691 fix mod condition 2023-10-23 11:22:28 +03:00
shimon b09a254d3c fix mod condition 2023-10-23 10:46:15 +03:00
shimon 76e2b39c1a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	app/controllers/api/projects.php
2023-10-23 01:08:04 +03:00
Christy JacobandGitHub e2eccaca8b Merge pull request #6985 from appwrite/fix-db-mod
Fix db mod
2023-10-23 02:07:15 +04:00
shimon a841734d50 fix mod condition 2023-10-23 01:05:10 +03:00
Christy JacobandGitHub 765391cd7c Merge branch 'main' into feat-support-selfdb 2023-10-23 01:56:54 +04:00
shimon ac231914d4 fix mod condition 2023-10-23 00:55:57 +03:00
shimon 453ce03881 fix mod condition 2023-10-23 00:47:48 +03:00
Christy JacobandGitHub d1deef43c5 Merge pull request #6984 from appwrite/fix-db-mod
fix mod condition
2023-10-23 01:11:05 +04:00
shimon 0d5127d90b fix mod condition 2023-10-23 00:08:55 +03:00
Christy JacobandGitHub bb46e3869c Merge pull request #6851 from appwrite/feat-sm-db
update projects database
2023-10-22 22:35:54 +04:00
德华 刘 00d2a5ffcd chore: update create projects endpoints 2023-10-22 18:33:15 +00:00
德华 刘 79612bd80f Merge branch 'main' of https://github.com/appwrite/appwrite into feat-sm-db 2023-10-22 18:30:14 +00:00
德华 刘 c0f099e176 chore: update test file 2023-10-22 15:28:39 +00:00
德华 刘 42ce9d881a Merge branch 'main' of https://github.com/appwrite/appwrite into chhore-parallelize-tests 2023-10-22 15:16:40 +00:00
Matej Bačo 1787e57448 Fix database override 2023-10-22 16:18:47 +02:00
Christy JacobandGitHub fc35e0f6ff Merge pull request #6954 from appwrite/1.4.x
Merge 1.4.x to main
2023-10-20 17:36:56 +04:00
prateek banga af40c63357 review changes 2023-10-20 17:02:13 +05:30
prateek banga e040ecba36 fixed lint error 2023-10-20 15:03:56 +05:30
prateek banga 6c260223ba Merge branch '1.5.x' of https://github.com/appwrite/appwrite into sync-1.4.x-to-1.5.x 2023-10-20 15:00:22 +05:30
prateek banga 161bbfb822 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into sync-1.4.x-to-1.5.x 2023-10-20 14:55:17 +05:30
Jake BarnbyandGitHub 8ed37fb445 Merge pull request #6950 from appwrite/feat-sms-push-controllers
Feat sms push controllers
2023-10-20 21:49:20 +13:00
Prateek Banga d714760eb8 review changeS 2023-10-20 13:51:13 +05:30
prateek banga 24b2d70441 review changes 2023-10-20 13:33:27 +05:30
prateek banga e4d2d15f70 review changes 2023-10-20 13:31:56 +05:30
prateek banga db9e7b0199 review changes 2023-10-20 13:28:23 +05:30
德华 刘 b0b9bd3268 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into chhore-parallelize-tests 2023-10-19 17:10:15 +00:00
德华 刘 9f3561aaa3 chore: cleanup ci file 2023-10-19 17:09:49 +00:00
Prateek BangaandGitHub a0f0413984 Merge branch '1.5.x' into feat-sms-push-controllers 2023-10-19 14:33:14 +05:30
prateek banga 99b5ac8669 adds push and sms controllers 2023-10-19 13:29:48 +05:30
Christy Jacob 6141d02e16 Merge branch 'refactor-workers-sn' of https://github.com/appwrite/appwrite into chhore-parallelize-tests 2023-10-18 21:49:43 +00:00
Vincent (Wen Yu) Ge 175879a352 remove package lock 2023-10-18 19:25:00 +00:00
Vincent (Wen Yu) Ge 397bdd50f4 Try even older commit for package-lock.json 2023-10-18 19:24:11 +00:00
Vincent (Wen Yu) Ge 413209d990 Fix checking out the wrong commit to revert package.json 2023-10-18 19:21:28 +00:00
Vincent (Wen Yu) Ge 3a80d58353 Revert package json changes 2023-10-18 19:14:57 +00:00
Jake BarnbyandGitHub 36d257c847 Merge pull request #6032 from appwrite/feat-topics-controller
Feat topics controller
2023-10-18 21:24:07 +13:00
Prateek Banga 55187d4781 fix typo 2023-10-18 13:30:01 +05:30
Khushboo Verma eac911a006 Remove VCS repo id from env variables 2023-10-18 13:27:29 +05:30
Khushboo Verma 979054e5ca Merge branch 'main' into refactor-vcs-tests 2023-10-18 13:25:40 +05:30
Khushboo Verma 2914a4d1c5 Add test for createRepo endpoint 2023-10-18 13:24:32 +05:30
Prateek Banga 4bef86d88b review changes 2023-10-18 13:19:21 +05:30
Jake Barnby bb0744842d Set metadata for all database instantiations 2023-10-18 16:21:10 +13:00
Steven NguyenandGitHub 9fbf10f9f5 Merge branch 'main' into fix-docs-links 2023-10-17 13:08:03 -07:00
Prateek Banga b86f531b30 adds data in message model and search param in messages query validator 2023-10-17 23:45:46 +05:30
Prateek Banga 8602c44d14 adds user targets graphql testsl 2023-10-17 23:29:10 +05:30
Prateek Banga ffeb3f8fcf review work 2023-10-17 22:53:26 +05:30
Torsten DittmannandGitHub 80574838a4 gitpod: fix composer usage 2023-10-17 15:19:29 +02:00
Jake Barnby 64d582a43a Update for package changes 2023-10-17 21:31:00 +13:00
Jake Barnby 62ce68f1b5 Update packages 2023-10-17 16:48:26 +13:00
Prateek Banga 4ccffb6649 adds graphql tests for topics, subcribers, email 2023-10-17 03:41:42 +05:30
Bradley Schofield 23a1da58fb Update Status Codes 2023-10-16 11:57:17 +01:00
Yatharth Verma 1489f77499 fix changes suggested by steven and also fixed some bugs came after merging with 1.4.x 2023-10-14 12:45:29 +05:30
Yatharth Verma 96f389cee5 merge 1.4.x changes to my branch 2023-10-14 11:57:40 +05:30
Yatharth Verma b420c570bc Reverted to last commit before merging main 2023-10-14 11:51:27 +05:30
Steven NguyenandGitHub 5956c4c247 Merge branch 'main' into fix-unique-in-errors 2023-10-13 12:03:23 -07:00
Bradley Schofield 5274f1234b Merge branch 'main' into feat-improve-migration-validations 2023-10-13 16:23:25 +01:00
Bradley Schofield d4b53e5014 Run Linter 2023-10-13 16:23:20 +01:00
Khushboo Verma fe7aa60a11 Updated composer.json 2023-10-13 18:55:33 +05:30
Khushboo Verma f18d33a0d6 Resolved merge conflicts 2023-10-13 18:54:14 +05:30
Khushboo Verma 66ec7f2e1d VCS tests 2023-10-13 18:50:58 +05:30
Prateek Banga feac25f401 adds already sent exception in update message 2023-10-13 18:35:49 +05:30
Prateek Banga 0b1b7646d5 review changes 2023-10-13 18:26:54 +05:30
Prateek Banga 4a44fb3330 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-10-13 14:52:02 +05:30
Khushboo Verma 615bd5148e Test for create and update function using VCS 2023-10-13 03:11:31 +05:30
Khushboo Verma bb75036efe WIP: Mocked GitHub installation 2023-10-13 01:45:27 +05:30
Jake BarnbyandGitHub 736b76cbfa Merge pull request #6023 from appwrite/feat-mailgun-provider
Feat provider controllers
2023-10-12 21:41:21 +13:00
Prateek Banga cc1769ed41 review changes 2023-10-12 11:48:26 +05:30
Vincent (Wen Yu) Ge 291654bee9 Rename ID -> Keys for attribute and index for consistency 2023-10-11 19:29:04 +00:00
Khushboo Verma dbd6dbcb5b WIP: Progress with VCS tests 2023-10-11 02:56:15 +05:30
Khushboo Verma 1f97bb39b3 WIP: VCS tests 2023-10-11 01:31:22 +05:30
Prateek Banga 17ba6355e0 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-10-10 19:52:19 +05:30
Prateek Banga bb52647366 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-10-10 19:06:39 +05:30
Torsten DittmannandGitHub 8882318d0d Merge pull request #6875 from appwrite/sync-1.5.x-with-1.4.x
Sync 1.5.x with 1.4.x
2023-10-10 15:35:17 +02:00
Prateek Banga 1e5a2a78aa Merge branch '1.4.x' of https://github.com/appwrite/appwrite into sync-1.5.x-with-1.4.x 2023-10-10 17:46:44 +05:30
Prateek Banga ecfdab1598 Merge branch 'main' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-10-09 17:30:50 +05:30
Prateek Banga a77a212e2c adds scheduling for messaging worker 2023-10-09 17:29:26 +05:30
Khushboo Verma bd2999037d WIP: Add VCS tests 2023-10-09 15:15:36 +05:30
shimon 31466c44d8 update projects database 2023-10-08 16:44:14 +03:00
Bradley Schofield 9cb1cf418c Improve Error Handling and Add more Validation 2023-10-06 17:02:01 +01:00
Prateek Banga a92390dda2 review changes 2023-10-06 19:23:46 +05:30
Prateek Banga d842191cc6 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-10-06 03:53:16 +05:30
Jake BarnbyandGitHub 76152019d6 Merge pull request #6514 from appwrite/sync-main-1.5.x
Sync main 1.5.x
2023-10-06 11:20:58 +13:00
Prateek Banga d5cc214546 Merge branch 'main' of https://github.com/appwrite/appwrite into sync-main-1.5.x 2023-10-06 03:44:36 +05:30
Prateek Banga 8e9f270ef0 update id param 2023-10-06 03:30:47 +05:30
Prateek Banga 22d445587e review changes 2023-10-05 16:57:48 +05:30
Prateek Banga fb98d21ae5 adds graphql test cases for provider endpoints 2023-10-05 02:12:04 +05:30
Vincent (Wen Yu) Ge e5c828fbbd Attribute keys must be unique and can't use unique() 2023-10-04 13:56:18 +00:00
Vincent (Wen Yu) Ge c12999dc74 Remove extra ` tags 2023-10-04 13:52:19 +00:00
Vincent (Wen Yu) Ge a2c9c8b82e Updated error message to not user magic strings 2023-10-04 13:49:45 +00:00
Prateek Banga 0251703e3a Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-10-04 17:32:16 +05:30
Prateek Banga 80a19f9862 unique to ID::unique 2023-10-04 17:30:40 +05:30
Prateek Banga abab68f1a1 fixes catching in messaging worker 2023-10-04 17:28:38 +05:30
Prateek Banga 5891872fad Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-10-04 17:27:56 +05:30
Prateek Banga c9c49158a3 changes to validator length 2023-10-04 16:24:29 +05:30
Prateek Banga 61785ab35a typo fix 2023-10-04 16:19:05 +05:30
Prateek Banga 48b91e39a8 review changes 2023-10-04 16:15:59 +05:30
Vincent (Wen Yu) GeandGitHub 9b0a2fdc73 Fix docs links CONTRIBUTING.md 2023-10-03 15:10:09 -04:00
Prateek Banga 1a50b2948f lint fix 2023-09-27 19:10:56 +05:30
Prateek Banga 7708779ef1 adds email endpoint test 2023-09-27 19:08:35 +05:30
Prateek Banga 90352c57ac Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-27 18:58:18 +05:30
Prateek Banga 234f7f003b updated test cases for msg91 provider, moves worker to use provider from settings 2023-09-27 18:58:00 +05:30
Prateek Banga 63bd4d6daf Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-27 16:57:07 +05:30
Prateek Banga a0858ccc4e adds message description, options to mailgun provider 2023-09-26 13:12:44 +05:30
Yatharth VermaandYatharth Verma 5771ad35ed fix test cases 2023-09-22 22:56:58 +05:30
Yatharth VermaandYatharth Verma 56a3b3df99 Add test cases 2023-09-22 22:56:58 +05:30
Yatharth Verma 6b47999120 fix conflicts 2023-09-22 22:56:07 +05:30
Yatharth Verma f07f246f8d fix conflicts 2023-09-22 22:53:41 +05:30
Prateek Banga b019273166 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-21 22:21:20 +05:30
Prateek Banga df6466a1d3 lint fix 2023-09-21 22:20:56 +05:30
Prateek Banga e00b273577 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-21 22:19:30 +05:30
Prateek Banga e2acb608fa fix tests with real sms providers 2023-09-21 22:16:02 +05:30
Prateek Banga d63eaf3ef0 fixes get message endpoint 2023-09-21 21:49:40 +05:30
Prateek Banga 06a05a3358 adds param messageId in send an email endpoint, tokenId as messageId in create phone session, membershipId as messageId in create team membership 2023-09-21 21:17:15 +05:30
Prateek Banga 3ebcfe548d Adds get message endpoint 2023-09-21 16:26:53 +05:30
Prateek Banga 342f122365 adds key auth to providers endpoint 2023-09-21 16:18:20 +05:30
Prateek Banga b4de44c905 fix small bug in worker and send email endpoint 2023-09-21 16:12:56 +05:30
Prateek Banga 39854239fb skip authorization in adding subscriber on targets 2023-09-21 02:29:57 +05:30
Prateek Banga 0510de5eab Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-20 23:37:35 +05:30
Prateek Banga 35d842171c review changes 2023-09-20 23:37:10 +05:30
Prateek Banga 3548c4ac47 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-20 23:03:11 +05:30
Prateek Banga dd14bbf785 lint fix 2023-09-20 23:02:53 +05:30
Prateek Banga 87011af914 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-20 23:00:48 +05:30
Prateek Banga 2fd7467e2a fix graphQL test 2023-09-20 22:59:47 +05:30
Prateek Banga 975b444761 fix test cases and bug fix in messaging worker 2023-09-20 20:51:03 +05:30
Prateek Banga 8d9e0ae7d0 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-20 17:53:08 +05:30
Prateek Banga 3456ad4e8c fixes merge conflict issue 2023-09-20 17:52:25 +05:30
Prateek Banga afef225339 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-20 17:39:36 +05:30
Prateek Banga f9030132a5 messaging worker logic to send message to providers batch limit 2023-09-20 17:38:29 +05:30
Prateek Banga a2e34b5b64 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-20 16:59:07 +05:30
Prateek Banga 67397cd763 fix tests and code changes 2023-09-20 15:55:22 +05:30
Prateek Banga bd36a09473 adds search in messaging controllers, updated mailgun endpoint with isEu param 2023-09-19 15:36:40 +05:30
Jake BarnbyandGitHub afd421e196 Merge pull request #5986 from appwrite/feat-messages-event-config
Feat messages event config
2023-09-14 17:01:19 -04:00
Prateek Banga 25fc64c4ab moves provider to top level in events config 2023-09-15 02:28:32 +05:30
Prateek Banga d2ce8155e6 Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-15 01:06:17 +05:30
Jake BarnbyandGitHub 250758a1d3 Merge pull request #5951 from appwrite/feat-add-messaging-response-models
Feat add messaging response models
2023-09-14 15:33:17 -04:00
prateek banga 7d51da2e69 review changes 2023-09-14 16:43:38 +05:30
prateek banga 2fe94be4c1 review changes 2023-09-14 16:34:53 +05:30
Prateek Banga 350549db7d Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-add-messaging-response-models 2023-09-14 02:17:34 +05:30
Jake BarnbyandGitHub 68dc92939f Merge pull request #6233 from appwrite/sync-1.4.x-to-1.5.x
Sync 1.4.x to 1.5.x
2023-09-13 16:47:00 -04:00
Prateek Banga dbd227ed50 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into sync-1.4.x-to-1.5.x 2023-09-14 02:13:27 +05:30
prateek banga 4bb611efc4 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-13 17:48:18 +05:30
prateek banga 0066216bef adds enabled param for provider endpoints and review changes 2023-09-13 17:47:38 +05:30
prateek banga 778056d8e0 Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-13 17:08:37 +05:30
prateek banga f472ae578f Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-13 17:02:56 +05:30
prateek banga 9b6fc440d9 Review changes and adds enabled in providers collection 2023-09-13 16:57:59 +05:30
Jake Barnby 1f9872b398 Merge remote-tracking branch 'origin/1.5.x' into feat-add-messaging-response-models 2023-09-12 15:40:25 -04:00
Jake BarnbyandGitHub d709682d10 Merge pull request #5930 from appwrite/feat-add-messaging-collections-in-config
add collections to config file and messaging scopes to config file
2023-09-12 15:38:21 -04:00
Prateek Banga fbd4712a72 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-13 00:54:02 +05:30
Prateek Banga d742b0c0a9 removes provider type from create user target 2023-09-13 00:53:25 +05:30
Prateek Banga 945d048b6e Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-13 00:51:08 +05:30
Prateek Banga 6d5cc588bf Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-13 00:50:38 +05:30
Prateek Banga 851afae29a change test provider to authProviders 2023-09-13 00:50:16 +05:30
Prateek Banga d14fbde9e7 removes provider sub query 2023-09-13 00:40:51 +05:30
Prateek Banga 420eb8857a Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-13 00:07:32 +05:30
Prateek Banga 6affa6b5a0 Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-13 00:07:09 +05:30
Prateek Banga a723744b06 Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-13 00:06:49 +05:30
Prateek Banga 11a458f1df change providers to authProviders in remaining places 2023-09-13 00:06:18 +05:30
Prateek Banga e653e68869 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-12 23:33:04 +05:30
Prateek Banga 445ab98267 Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-12 23:32:30 +05:30
Prateek Banga 6631406236 Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-12 23:32:09 +05:30
Prateek Banga 7dd25f60f3 update console submodule changes 2023-09-12 23:30:04 +05:30
Prateek Banga 118a50b894 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-12 22:07:17 +05:30
Prateek Banga 720c7300dc Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-12 22:06:31 +05:30
Prateek Banga 68365babd1 Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-12 22:05:45 +05:30
Prateek Banga 497586378f removes unnecessary config load 2023-09-12 22:05:14 +05:30
Prateek Banga 9fbad72b08 Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-add-messaging-response-models 2023-09-12 22:03:34 +05:30
Prateek Banga 5136724a29 fix default,type index 2023-09-12 22:02:19 +05:30
Prateek Banga b54556d10e Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-12 21:31:45 +05:30
Prateek Banga a69368973b Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-12 21:31:06 +05:30
Prateek Banga 878e56ae7e Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-12 21:24:11 +05:30
Prateek Banga d14a4c2506 Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-add-messaging-response-models 2023-09-12 21:23:18 +05:30
Prateek Banga 2f2875e3c0 lint fix 2023-09-12 21:19:21 +05:30
Prateek Banga ba96f71a3f Merge branch '1.5.x' of https://github.com/appwrite/appwrite into feat-add-messaging-collections-in-config 2023-09-12 21:15:00 +05:30
Prateek Banga b7119a2718 review changes 2023-09-12 21:03:44 +05:30
Prateek Banga cbea4264ea merge master 2023-09-12 20:53:16 +05:30
Prateek Banga 996de004fb Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-11 20:41:52 +05:30
Prateek Banga ddbee0a55b Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-11 20:41:12 +05:30
Prateek Banga 8957640d27 Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-11 20:40:32 +05:30
Prateek Banga 78a2f6f246 review changes 2023-09-11 20:36:43 +05:30
Prateek Banga 899c962061 Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-add-messaging-response-models 2023-09-11 20:30:30 +05:30
Prateek Banga a59cfb6461 review changes 2023-09-11 20:30:21 +05:30
Prateek Banga 32970ba8a0 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-11 19:54:24 +05:30
Prateek Banga 2ecf9ee98a udpates send email endpoint according to collection changes 2023-09-11 19:53:48 +05:30
Prateek Banga 27741644e8 Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-11 19:52:48 +05:30
Prateek Banga f4f1aa4b5b review changes and adds missing subqueries 2023-09-11 19:27:45 +05:30
Christy Jacob ab5fa96668 chore: update ci file 2023-09-09 02:39:51 +00:00
Christy Jacob 8a28c9ddbc chore: update ci file 2023-09-09 02:29:50 +00:00
Christy Jacob 85fcddcead chore: update ci file 2023-09-09 02:20:25 +00:00
Christy Jacob ff4816a436 chore: update ci file 2023-09-09 02:17:21 +00:00
Christy Jacob 1c9dfb903d chore: update ci file 2023-09-09 01:58:37 +00:00
Christy Jacob 238089409a chore: update ci file 2023-09-09 01:39:01 +00:00
Christy Jacob e70d5beb0d chore: update ci file 2023-09-09 01:29:56 +00:00
Christy Jacob e390cf0d70 chore: update CI file 2023-09-09 00:11:14 +00:00
Christy Jacob 29e056c71b chore: update CI file 2023-09-09 00:00:06 +00:00
Christy Jacob 0a2874458b chore: update CI file 2023-09-08 23:54:27 +00:00
Christy Jacob 0ddf036bd5 chore: update CI file 2023-09-08 23:44:19 +00:00
Christy Jacob 29d0820b10 chore: update CI file 2023-09-08 23:41:16 +00:00
Christy Jacob 889942ee76 chore: update CI file 2023-09-08 23:37:41 +00:00
Christy Jacob 01aaa6b337 chore: update CI file 2023-09-08 23:34:23 +00:00
Christy Jacob eaf94aa670 chore: update CI file 2023-09-08 23:31:04 +00:00
Christy Jacob 1a93aa4085 chore: update CI file 2023-09-08 23:24:16 +00:00
Christy Jacob 0f67c1151f chore: update CI file 2023-09-08 23:18:26 +00:00
Christy Jacob 10f6cc7b0b chore: update CI file 2023-09-08 23:12:11 +00:00
Christy Jacob 733b7ed612 chore: update CI file 2023-09-08 22:57:25 +00:00
Christy Jacob 1415727f10 chore: update CI file 2023-09-08 22:53:50 +00:00
Christy Jacob ebb4bc7e6b chore: update CI file 2023-09-08 22:39:11 +00:00
Christy Jacob 21357bfe7b chore: update CI file 2023-09-08 22:32:54 +00:00
Christy Jacob 5cb14fbec5 feat: test upload artifact 2023-09-08 22:11:40 +00:00
Christy Jacob 5793a11480 feat: test upload artifact 2023-09-08 22:10:56 +00:00
Christy Jacob 2ed6327c1e feat: test upload artifact 2023-09-08 22:03:32 +00:00
Christy Jacob cf030457e0 feat: test upload artifact 2023-09-08 21:55:28 +00:00
Christy Jacob 27f77759ed feat: test upload artifact 2023-09-08 21:48:31 +00:00
Christy Jacob dec4f4722b feat: test upload artifact 2023-09-08 21:43:18 +00:00
Christy Jacob 8bd822d171 feat: test upload artifact 2023-09-08 21:35:01 +00:00
Christy Jacob 13c6666883 feat: test upload artifact 2023-09-08 21:29:11 +00:00
Christy Jacob fa99c4584e feat: test upload artifact 2023-09-08 20:07:13 +00:00
Prateek Banga cf7b63fd2f Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-09 01:19:42 +05:30
Prateek Banga ee0f789e37 udpates deliveryError to be an array type 2023-09-09 01:18:26 +05:30
Christy Jacob 2aad9e622b feat: trigger ci 2023-09-08 19:16:25 +00:00
Christy Jacob 22c8781047 feat: test upload artifact 2023-09-08 19:11:57 +00:00
Christy Jacob 57e146be2b feat: test upload artifact 2023-09-08 19:11:00 +00:00
Christy Jacob 1e07fa96a3 feat: test upload artifact 2023-09-08 19:06:10 +00:00
Christy Jacob 2eae37d802 feat: test upload artifact 2023-09-08 19:03:55 +00:00
Christy Jacob addc88ccdc chore: test parallel executions 2023-09-08 18:49:02 +00:00
prateek banga a81006e961 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-06 16:31:07 +05:30
prateek banga 15773a3561 sets the correct response for send email 2023-09-06 16:30:46 +05:30
prateek banga 06361828a9 Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-06 15:41:19 +05:30
prateek banga 039e717d72 fixes in send email endpoint 2023-09-06 15:40:56 +05:30
prateek banga 3d286a2171 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-09-05 23:10:59 +05:30
prateek banga 38d81c058b lint fix 2023-09-05 23:10:33 +05:30
prateek banga 6aa443454b Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller 2023-09-05 22:58:43 +05:30
prateek banga 00498f0ae2 Merge branch 'feat-messages-event-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-05 22:58:14 +05:30
prateek banga 597fd965f7 Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-messages-event-config 2023-09-05 22:57:48 +05:30
prateek banga 4d31cd44d5 Merge branch 'feat-add-messaging-providers' of https://github.com/appwrite/appwrite into feat-add-messaging-response-models 2023-09-05 22:57:04 +05:30
prateek banga 45ab78caa7 Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-add-messaging-providers 2023-09-05 22:56:37 +05:30
prateek banga 78a53aee37 adds messaging event and updates messaging worker 2023-09-05 22:40:48 +05:30
prateek banga 01d5984965 Merge branch 'feat-add-messaging-response-models' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-05 13:34:10 +05:30
prateek banga 1a791f46e6 adds default in provider response model 2023-09-05 13:33:22 +05:30
prateek banga 337839103f Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-09-05 13:15:12 +05:30
prateek banga f4eac9f41d Merge branch 'feat-add-messaging-collections-in-config' into feat-mailgun-provider 2023-09-05 13:14:13 +05:30
prateek banga 3f95d6abd5 Merge branch 'feat-add-messaging-collections-in-config' of https://github.com/appwrite/appwrite into feat-add-messaging-collections-in-config 2023-09-05 01:34:02 +05:30
prateek banga ea324262e7 updated messaging collectin 2023-09-05 01:33:50 +05:30
prateek banga 8b2561d55a Merge branch '1.5.x' into feat-topics-controller 2023-08-31 19:54:45 +05:30
prateek banga 0b7d290419 Merge branch '1.5.x' into feat-mailgun-provider 2023-08-31 19:54:22 +05:30
prateek banga c263a7b130 Merge branch '1.5.x' into feat-messages-event-config 2023-08-31 19:53:35 +05:30
prateek banga 180493d642 Merge branch '1.5.x' into feat-add-messaging-response-models 2023-08-31 19:53:15 +05:30
prateek banga 0ed2729b86 Merge branch '1.5.x' into feat-add-messaging-providers 2023-08-31 19:50:58 +05:30
prateek banga 0c76cfcb00 Merge branch '1.5.x' into feat-add-messaging-collections-in-config 2023-08-31 19:50:20 +05:30
Eldad A. FuxandGitHub 0a289bce43 Merge pull request #6092 from appwrite/sync-master-into-1.5.x
Sync master into 1.5.x
2023-08-31 16:31:49 +03:00
prateek banga 2e89837c13 Merge branch 'master' into sync-master-into-1.5.x 2023-08-31 18:41:32 +05:30
prateek banga 82e165c4c3 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-31 18:33:02 +05:30
prateek banga 1762c81b8d Merge branch 'feat-messages-event-config' into feat-mailgun-provider 2023-08-31 18:32:26 +05:30
prateek banga 0534b11b32 Merge branch 'feat-add-messaging-response-models' into feat-messages-event-config 2023-08-31 18:26:32 +05:30
prateek banga 32b48a3c7c removes user id from subscriber model 2023-08-31 18:22:36 +05:30
prateek banga 8d14dcb839 Merge branch 'feat-add-messaging-providers' into feat-add-messaging-response-models 2023-08-31 18:18:59 +05:30
prateek banga e104e6d0f2 Merge branch 'feat-add-messaging-collections-in-config' into feat-add-messaging-providers 2023-08-31 18:16:48 +05:30
prateek banga 7fd124705c merge master 2023-08-31 18:11:56 +05:30
prateek banga 24eabf29d0 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-29 22:30:02 +05:30
prateek banga 112c7a5133 adds default attribute for providers 2023-08-29 22:29:48 +05:30
prateek banga 75d7f716e8 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into feat-mailgun-provider 2023-08-29 22:13:12 +05:30
prateek banga e53af019da Revert "Working on messaging tests with worker"
This reverts commit c2c70d68e9.
2023-08-29 21:43:21 +05:30
wess c2c70d68e9 Working on messaging tests with worker 2023-08-28 16:27:55 -04:00
prateek banga 1d6276eb9f lint fix 2023-08-28 20:43:18 +05:30
prateek banga 491d58f146 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-28 20:42:12 +05:30
prateek banga 379f7b9aa2 fixes subquery attribute in collection config 2023-08-28 20:41:47 +05:30
prateek banga 264f0bd5dd adds subscriber controllers 2023-08-28 20:41:05 +05:30
prateek banga f743bf384b Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 19:48:40 +05:30
prateek banga 2c4e90d660 removes unnecessary test case 2023-08-25 19:48:21 +05:30
prateek banga 639fae3156 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 16:03:37 +05:30
prateek banga d7c8562260 removes target controllers from account service 2023-08-25 16:02:46 +05:30
prateek banga 9567bf3943 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 15:55:54 +05:30
prateek banga c59887e8fb fixes issue in sub queries 2023-08-25 15:55:04 +05:30
prateek banga de6bcc1861 fixes test 2023-08-25 14:40:48 +05:30
prateek banga 24853004f9 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 04:49:02 +05:30
prateek banga 9be0592dab lint fix 2023-08-25 04:46:15 +05:30
prateek banga a266682951 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 04:44:13 +05:30
prateek banga 94b7f67e44 changes id to providerId for provider create controllers 2023-08-25 04:43:40 +05:30
prateek banga d0c50403c8 adds test cases for topic controller 2023-08-25 04:36:41 +05:30
prateek banga b8f8fb1708 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 03:09:43 +05:30
prateek banga 5960666068 added messaging base test 2023-08-25 03:09:12 +05:30
prateek banga f835e1b52e Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 03:01:38 +05:30
prateek banga 048e8e81ba removes permission from provider controllers 2023-08-25 02:59:06 +05:30
prateek banga 33028ebbf2 Merge branch 'feat-mailgun-provider' into feat-topics-controller 2023-08-25 02:34:10 +05:30
prateek banga 33f4bd6ac4 adds custom id for provider controlers, fixes subquery 2023-08-25 02:32:51 +05:30
prateek banga 52f6b72f23 adds topic controllers 2023-08-25 01:45:17 +05:30
prateek banga df77f92860 adds sub query for targets and topics, permission for user target 2023-08-25 01:22:16 +05:30
prateek banga a87745f685 fix tests 2023-08-24 15:37:59 +05:30
prateek banga eafea1c41d formats messaging controller 2023-08-24 14:31:22 +05:30
prateek banga 5ca8dc0a73 merge cl-1.4.x 2023-08-24 14:10:26 +05:30
prateek banga e8cc60b0c2 adds audit resource in messaging controllers 2023-08-24 14:05:51 +05:30
prateek banga 82d220e158 fixes audit events 2023-08-24 13:43:30 +05:30
prateek banga a95c29d546 lint fix 2023-08-24 01:55:08 +05:30
prateek banga ec54000263 adds test for target controllers 2023-08-24 01:54:25 +05:30
prateek banga ac33d7828f Revert "Pint linter ran"
This reverts commit bf5f0a8419.
2023-08-24 01:53:52 +05:30
Jake BarnbyandGitHub 145b0f586d Merge pull request #6030 from appwrite/cl-1.4.x
Sync 1.5.x
2023-08-23 15:10:57 -04:00
wess bf5f0a8419 Pint linter ran 2023-08-23 13:12:43 -04:00
wess eba91ae55a Stubbing in tests and message builder 2023-08-23 12:22:04 -04:00
wess 3e7805afe1 Updates deps
Adds message builders to messaging worker

Adds Makefile to .gitignore ;)
2023-08-23 08:49:32 -04:00
prateek banga 55d00d9afa lint fix 2023-08-23 15:36:12 +05:30
prateek banga c225563d77 adds test for provider controllers 2023-08-23 15:31:29 +05:30
prateek banga dbbf4522e3 lint fix 2023-08-22 22:17:32 +05:30
prateek banga ab89abfc89 lint fix 2023-08-22 22:16:09 +05:30
prateek banga 78385a59c6 adds remaining provider controllers 2023-08-22 22:15:31 +05:30
prateek banga 9f19bec7a7 lint fix 2023-08-22 18:21:30 +05:30
prateek banga 4406c7bd55 adds messaging worker and send an email controller 2023-08-21 22:15:15 +05:30
prateek banga a6d613542b adds messaging api controlle for mailgun provider 2023-08-21 16:14:50 +05:30
prateek banga fe8b8d3dc6 fix lint 2023-08-21 14:14:06 +05:30
prateek banga 5e3065c30a merge resolve conflicts 2023-08-21 14:07:18 +05:30
prateek banga c0e4149feb Merge branch 'feat-messages-event-config' into feat-target-controllers-account-user 2023-08-18 23:35:00 +05:30
prateek banga cc1311b723 Merge branch 'feat-add-messaging-response-models' into feat-messages-event-config 2023-08-18 23:33:59 +05:30
prateek banga a174aca2c8 lint fix and rename subscriber model file correctly 2023-08-18 23:30:42 +05:30
prateek banga 36a5d1e245 adds remaining controllers for target in account and users 2023-08-18 23:28:12 +05:30
prateek banga f73b3bf272 deletes user cache document 2023-08-18 01:07:42 +05:30
prateek banga c83e4db23d lint fix 2023-08-17 19:02:07 +05:30
prateek banga 07b7a9db0f adds controller for creating a user target 2023-08-17 17:24:45 +05:30
prateek banga 455176d077 Merge branch 'feat-add-messaging-collections-in-config' into feat-target-controllers-account-user 2023-08-16 20:29:19 +05:30
prateek banga 40f419663c removes id attribute from collections config 2023-08-16 20:28:40 +05:30
prateek banga e53f189b3b lint fix 2023-08-16 20:17:36 +05:30
prateek banga c7dec3762b Adds getTarget and list targets controller to user and account 2023-08-16 20:13:38 +05:30
prateek banga ab655e69e6 Merge branch 'feat-add-messaging-collections-in-config' into feat-messages-event-config 2023-08-16 18:49:00 +05:30
prateek banga 9fc75e5535 moves messaging collection to common, adds subqueryTargets 2023-08-16 18:48:38 +05:30
prateek banga 37e5cecfb1 resolve merge conflicts 2023-08-16 18:35:15 +05:30
prateek banga caad1a07d9 resolve merge conflicts 2023-08-16 18:21:04 +05:30
prateek banga e2e4287801 resolve merge conflicts 2023-08-16 15:19:16 +05:30
prateek banga 45e03d3973 Merge branch 'feat-add-messaging-providers' into feat-add-messaging-response-models 2023-08-16 15:17:53 +05:30
prateek banga 92e587769a resolve merge conflicts 2023-08-16 15:16:45 +05:30
prateek banga 4b63a4abbb resolve merge conflicts 2023-08-14 21:13:47 +05:30
prateek banga 296a54d0ce adds messages event config and target attribute in user model 2023-08-14 21:11:24 +05:30
prateek banga d7f806e671 fix lint issuesl 2023-08-07 21:01:25 +05:30
prateek banga 3e70a19e4f add response models required for messaging service 2023-08-07 20:56:03 +05:30
prateek banga be1afc61b0 rename provider model to AuthProvider 2023-08-07 16:19:55 +05:30
prateek banga 9057a50a10 rename provider param in config 2023-08-07 15:54:00 +05:30
prateek banga 5baa0d674a rename providers.php to authProviders.php in docs and test 2023-08-07 14:17:02 +05:30
prateek banga 4ca798f4d7 fix lint issues 2023-08-04 19:01:55 +05:30
prateek banga 867f3e8c08 adds messaging providers rename providers.php to authProviders.php 2023-08-04 19:00:08 +05:30
prateek banga 19fd30d6d8 fix lint issues 2023-08-04 15:01:26 +05:30
prateek banga 13e73d8558 add scopes required for messaging service 2023-08-04 14:55:03 +05:30
prateek banga 6108be6ae0 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into feat-add-messaging-collections-in-config 2023-08-04 14:31:10 +05:30
prateek banga 2d33681a0b add collections to config file 2023-08-02 20:28:30 +05:30
120 changed files with 1112372 additions and 1555 deletions
+8 -3
View File
@@ -87,15 +87,20 @@ _APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=3
_APP_GRAPHQL_MAX_DEPTH=4
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=
_APP_VCS_GITHUB_APP_NAME=
_APP_VCS_GITHUB_PRIVATE_KEY=""
_APP_VCS_GITHUB_PRIVATE_KEY=disabled
_APP_VCS_GITHUB_APP_ID=
_APP_VCS_GITHUB_CLIENT_ID=
_APP_VCS_GITHUB_CLIENT_SECRET=
_APP_VCS_GITHUB_WEBHOOK_SECRET=
_APP_MIGRATIONS_FIREBASE_CLIENT_ID=
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
_APP_ASSISTANT_OPENAI_API_KEY=
_APP_ASSISTANT_OPENAI_API_KEY=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
_APP_MESSAGE_SMS_TEST_DSN=
_APP_MESSAGE_EMAIL_TEST_DSN=
_APP_MESSAGE_PUSH_TEST_DSN=
_APP_CONSOLE_SUPABASE_APP_ID=d64cd75f-2786-4b94-aeda-eae01e165941
_APP_CONSOLE_SUPABASE_SECRET=sba_d5a802fbdcdf86d27f7d071a2778bff0419040de
+105 -42
View File
@@ -4,57 +4,120 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
IMAGE: appwrite-dev
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
on: [pull_request]
jobs:
tests:
name: Unit & E2E
setup:
name: Setup & Build Appwrite Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Appwrite
uses: docker/build-push-action@v3
with:
context: .
push: false
tags: ${{ env.IMAGE }}
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
build-args: |
DEBUG=false
TESTING=true
VERSION=dev
- name: Cache Docker Image
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
unit_test:
name: Unit Test
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# Fetch submodules
submodules: recursive
- name: checkout
uses: actions/checkout@v2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Load Cache
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
# This is a separate action that sets up buildx runner
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 10
- name: Build Appwrite
uses: docker/build-push-action@v3
with:
context: .
push: false
tags: appwrite-dev
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
DEBUG=false
TESTING=true
VERSION=dev
- name: Doctor
run: docker compose exec -T appwrite doctor
- name: Start Appwrite
run: |
docker compose up -d
sleep 30
- name: Environment Variables
run: docker compose exec -T appwrite vars
- name: Doctor
run: |
docker compose logs appwrite
docker compose exec -T appwrite doctor
- name: Run Unit Tests
run: docker compose exec appwrite test /usr/src/code/tests/unit
- name: Environment Variables
run: docker compose exec -T appwrite vars
e2e_test:
name: E2E Test
runs-on: ubuntu-latest
needs: setup
strategy:
fail-fast: false
matrix:
service:
[
Account,
Avatars,
Console,
Databases,
Functions,
GraphQL,
Health,
Locale,
Projects,
Realtime,
Storage,
Teams,
Users,
Webhooks,
VCS,
Messaging,
]
- name: Run Tests
run: docker compose exec -T appwrite test --debug
steps:
- name: checkout
uses: actions/checkout@v3
- name: Load Cache
uses: actions/cache@v3
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
docker compose up -d
sleep 10
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
+1
View File
@@ -13,3 +13,4 @@ debug/
app/sdks
dev/yasd_init.php
.phpunit.result.cache
Makefile
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 3.2.3
branch = 3.2.7
+1 -1
View File
@@ -8,7 +8,7 @@ tasks:
command: |
docker run --rm --interactive --tty \
--volume $PWD:/app \
composer update \
composer install \
--ignore-platform-reqs \
--optimize-autoloader \
--no-plugins \
+71 -1
View File
@@ -1,3 +1,73 @@
# Version 1.4.12
## Miscellaneous
* Bump console to version 3.2.7 [#7148](https://github.com/appwrite/appwrite/pull/7148)
* Chore update database to 0.45.2 [#7138](https://github.com/appwrite/appwrite/pull/7138)
* Implement queue thresholds for the health API [#7123](https://github.com/appwrite/appwrite/pull/7123)
* Add Authorization::skip to the usage worker [#7124](https://github.com/appwrite/appwrite/pull/7124)
## Bug fixes
* fix: use queueForDeletes in git installation delete endpoint [#7140](https://github.com/appwrite/appwrite/pull/7140)
* fix: patch script, make errors silent [#7134](https://github.com/appwrite/appwrite/pull/7134)
* fix: repositories recreation script [#7133](https://github.com/appwrite/appwrite/pull/7133)
* fix: Only delete repositories linked to the particular project [#7131](https://github.com/appwrite/appwrite/pull/7131)
# Version 1.4.11
## Miscellaneous
* Update database by @abnegate in [#7111](https://github.com/appwrite/appwrite/pull/7111)
# Version 1.4.10
## Bug fixes
* Handle cases where password history could contain NULLs [#7092](https://github.com/appwrite/appwrite/pull/7092)
* Missing functionId error on create execution [#7091](https://github.com/appwrite/appwrite/pull/7091)
* Ensure usage endpoints don't throw 500 when usage is disabled [#7087](https://github.com/appwrite/appwrite/pull/7087)
* Missing sessionId error when deleting all user sessions [#7085](https://github.com/appwrite/appwrite/pull/7085)
* Domain validation in Create Proxy rule results in 500 error [#7084](https://github.com/appwrite/appwrite/pull/7084)
* Fix optional services [#7078](https://github.com/appwrite/appwrite/pull/7078)
* Fix regression from worker refactor [#7074](https://github.com/appwrite/appwrite/pull/7074)
* Use getQueueSize() in the Health service's get X queue endpoints [#7073](https://github.com/appwrite/appwrite/pull/7073)
* Delete linked VCS repos and comments [#7066](https://github.com/appwrite/appwrite/pull/7066)
# Version 1.4.9
## Bug fixes
* Fix 400 error on function domain execution in [#7059](https://github.com/appwrite/appwrite/pull/7059)
# Version 1.4.8
## Notable changes
* Fix certificate emails and add support for variables in email template subject in [#6495](https://github.com/appwrite/appwrite/)pull/6495
* Bump console to version 3.2.5 in [#7027](https://github.com/appwrite/appwrite/pull/7027)
* Bump utopia database and storage versions in [#7002](https://github.com/appwrite/appwrite/pull/7002)
## Bug fixes
* Fixes cookie headers not being passed properly by router in [#7024](https://github.com/appwrite/appwrite/pull/7024)
* Fix permission problem in deletes worker in [#7013](https://github.com/appwrite/appwrite/pull/7013)
## Miscellaneous
* Improve error handling in the realtime service in [#6998](https://github.com/appwrite/appwrite/pull/6998)
* Update the error code for unsupported protocol in [#7006](https://github.com/appwrite/appwrite/pull/7006)
* Improve CI tests by executing them in parallel in [#6198](https://github.com/appwrite/appwrite/pull/6198)
* Update README.md to add links to orchestration tools in [#7011](https://github.com/appwrite/appwrite/pull/7011)
* Update gitpod setup to install instead of update dependencies in [#6938](https://github.com/appwrite/appwrite/pull/6938)
* Remove analytics from install script in [#7017](https://github.com/appwrite/appwrite/pull/7017)
* Improve database logging in [#7003](https://github.com/appwrite/appwrite/pull/7003)
* Add VCS tests in [#6894](https://github.com/appwrite/appwrite/pull/6894)
* Improve error messages in [#6487](https://github.com/appwrite/appwrite/pull/6487)
* Add command to delete orphaned projects in [#7015](https://github.com/appwrite/appwrite/pull/7015)
# Version 1.4.7
## Fixes
- Fix missing body in async function execution in [#6988](https://github.com/appwrite/appwrite/pull/6988)
# Version 1.4.6
## Changes
@@ -396,7 +466,7 @@
## Features
- Added Phone Authentication by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3357
- Added Twilio Support
- Added TextMagic Support
- Added Textmagic Support
- Added Telesign Support
- Added Endpoint to create Phone Session (`POST:/v1/account/sessions/phone`)
- Added Endpoint to confirm Phone Session (`PUT:/v1/account/sessions/phone`)
+2 -2
View File
@@ -89,7 +89,7 @@ $ git push origin [name_of_your_new_branch]
To set up a working **development environment**, just fork the project git repository and install the backend and frontend dependencies using the proper package manager and create run the docker-compose stack.
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/getting-started-for-web), or the main [README](README.md) file.
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/quick-starts), or the main [README](README.md) file.
```bash
git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git
@@ -454,7 +454,7 @@ From time to time, our team will add tutorials that will help contributors find
- [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
- [Appwrite Environment Variables](./docs/tutorials/add-environment-variable.md)
- [Running in Production](https://appwrite.io/docs/production)
- [Running in Production](https://appwrite.io/docs/advanced/self-hosting/production)
- [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
## Other Ways to Help
+2
View File
@@ -100,7 +100,9 @@ RUN chmod +x /usr/local/bin/doctor && \
RUN chmod +x /usr/local/bin/hamster && \
chmod +x /usr/local/bin/volume-sync && \
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
chmod +x /usr/local/bin/patch-recreate-repositories-documents && \
chmod +x /usr/local/bin/patch-delete-project-collections && \
chmod +x /usr/local/bin/delete-orphaned-projects && \
chmod +x /usr/local/bin/clear-card-cache && \
chmod +x /usr/local/bin/calc-users-stats && \
chmod +x /usr/local/bin/calc-tier-stats
+4 -4
View File
@@ -55,7 +55,7 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
## 安装
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 [Kubernetes](https://kubernetes.io/docs/home/)、[Docker Swarm](https://docs.docker.com/engine/swarm/) 或 [Rancher](https://rancher.com/docs/))上运行 Appwrite。
启动 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html)
@@ -66,7 +66,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.12
```
### Windows
@@ -78,7 +78,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.12
```
#### PowerShell
@@ -88,7 +88,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.12
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+4 -4
View File
@@ -65,7 +65,7 @@ Table of Contents:
## Installation
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as Kubernetes, Docker Swarm, or Rancher.
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as [Kubernetes](https://kubernetes.io/docs/home/), [Docker Swarm](https://docs.docker.com/engine/swarm/), or [Rancher](https://rancher.com/docs/).
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
@@ -76,7 +76,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.12
```
### Windows
@@ -88,7 +88,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.12
```
#### PowerShell
@@ -98,7 +98,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.12
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -61,7 +61,11 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
->getResource();
$dbForConsole = new Database($dbAdapter, $cache);
$dbForConsole->setNamespace('_console');
$dbForConsole
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
// Ensure tables exist
$collections = Config::getParam('collections', [])['console'];
@@ -111,7 +115,10 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
$databases[$databaseName] = $database;
$database->setNamespace('_' . $project->getInternalId());
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
};
+579 -3
View File
@@ -5,7 +5,7 @@ use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
$providers = Config::getParam('providers', []);
$providers = Config::getParam('oAuthProviders', []);
$auth = Config::getParam('auth', []);
/**
@@ -221,6 +221,17 @@ $commonCollections = [
'array' => false,
'filters' => ['subQueryMemberships'],
],
[
'$id' => ID::custom('targets'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryTargets'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
@@ -670,6 +681,17 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('expire'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
],
'indexes' => [
[
@@ -1366,7 +1388,561 @@ $commonCollections = [
],
],
],
];
'providers' => [
'$collection' => ID::custom(DATABASE::METADATA),
'$id' => ID::custom('providers'),
'name' => 'Providers',
'attributes' => [
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('provider'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('type'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('enabled'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => true,
'array' => false,
],
[
'$id' => ID::custom('credentials'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => ['json', 'encrypt'],
],
[
'$id' => ID::custom('options'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => [],
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => ['providerSearch'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_provider'),
'type' => Database::INDEX_KEY,
'attributes' => ['provider'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_type'),
'type' => Database::INDEX_KEY,
'attributes' => ['type'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled_type'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled','type'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
]
],
],
'messages' => [
'$collection' => ID::custom(DATABASE::METADATA),
'$id' => ID::custom('messages'),
'name' => 'Messages',
'attributes' => [
[
'$id' => ID::custom('providerType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('description'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('status'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => 'processing',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('data'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('topics'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 21845,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('users'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 21845,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('targets'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 21845,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('scheduledAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('deliveredAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('deliveryErrors'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('deliveredTotal'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => ['messageSearch'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
],
],
'topics' => [
'$collection' => ID::custom(DATABASE::METADATA),
'$id' => ID::custom('topics'),
'name' => 'Topics',
'attributes' => [
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('description'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('total'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('targets'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryTopicTargets'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => ['topicSearch'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
]
],
],
'subscribers' => [
'$collection' => ID::custom(DATABASE::METADATA),
'$id' => ID::custom('subscribers'),
'name' => 'Subscribers',
'attributes' => [
[
'$id' => ID::custom('targetId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('targetInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('topicId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('topicInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_targetId'),
'type' => Database::INDEX_KEY,
'attributes' => ['targetId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_targetInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['targetInternalId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_topicId'),
'type' => Database::INDEX_KEY,
'attributes' => ['topicId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_topicInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['topicInternalId'],
'lengths' => [],
'orders' => [],
]
],
],
'targets' => [
'$collection' => ID::custom(DATABASE::METADATA),
'$id' => ID::custom('targets'),
'name' => 'Targets',
'attributes' => [
[
'$id' => ID::custom('userId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('userInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('identifier'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_userId'),
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_userInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['userInternalId'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_providerId'),
'type' => Database::INDEX_KEY,
'attributes' => ['providerId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_providerInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['providerInternalId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_identifier'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['identifier'],
'lengths' => [],
'orders' => [],
],
],
],
];
$projectCollections = array_merge([
'databases' => [
@@ -3412,7 +3988,7 @@ $consoleCollections = array_merge([
'filters' => ['json'],
],
[
'$id' => ID::custom('authProviders'),
'$id' => ID::custom('oAuthProviders'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
+133 -7
View File
@@ -86,7 +86,7 @@ return [
Exception::GENERAL_PROTOCOL_UNSUPPORTED => [
'name' => Exception::GENERAL_PROTOCOL_UNSUPPORTED,
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
'code' => 500,
'code' => 426,
],
Exception::GENERAL_CODES_DISABLED => [
'name' => Exception::GENERAL_CODES_DISABLED,
@@ -103,6 +103,16 @@ return [
'description' => 'This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version.',
'code' => 405,
],
Exception::GENERAL_INVALID_EMAIL => [
'name' => Exception::GENERAL_INVALID_EMAIL,
'description' => 'Value must be a valid email address.',
'code' => 400,
],
Exception::GENERAL_INVALID_PHONE => [
'name' => Exception::GENERAL_INVALID_PHONE,
'description' => 'Value must be a valid phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.',
'code' => 400,
],
/** User Errors */
Exception::USER_COUNT_EXCEEDED => [
@@ -190,6 +200,16 @@ return [
'description' => 'The password you are trying to use contains references to your name, email, phone or userID. For your security, please choose a different password and try again.',
'code' => 400,
],
Exception::USER_PASSWORD_DICTIONARY => [
'name' => Exception::USER_PASSWORD_DICTIONARY,
'description' => 'Password must be at least 8 characters and should not be one of the commonly used password.',
'code' => 400,
],
Exception::USER_PASSWORD_AI => [
'name' => Exception::USER_PASSWORD_AI,
'description' => 'As per AI, your password is not strong enough. Please choose a different password and try again.',
'code' => 400,
],
Exception::USER_SESSION_NOT_FOUND => [
'name' => Exception::USER_SESSION_NOT_FOUND,
'description' => 'The current user session could not be found.',
@@ -240,6 +260,26 @@ return [
'description' => 'OAuth2 provider returned some error.',
'code' => 424,
],
Exception::USER_EMAIL_ALREADY_VERIFIED => [
'name' => Exception::USER_EMAIL_ALREADY_VERIFIED,
'description' => 'User email is already verified',
'code' => 409,
],
Exception::USER_PHONE_ALREADY_VERIFIED => [
'name' => Exception::USER_PHONE_ALREADY_VERIFIED,
'description' => 'User phone is already verified',
'code' => 409
],
Exception::USER_TARGET_NOT_FOUND => [
'name' => Exception::USER_TARGET_NOT_FOUND,
'description' => 'The target could not be found.',
'code' => 404,
],
Exception::USER_TARGET_ALREADY_EXISTS => [
'name' => Exception::USER_TARGET_ALREADY_EXISTS,
'description' => 'A target with the same ID already exists.',
'code' => 409,
],
/** Teams */
Exception::TEAM_NOT_FOUND => [
@@ -355,7 +395,7 @@ return [
],
Exception::STORAGE_BUCKET_ALREADY_EXISTS => [
'name' => Exception::STORAGE_BUCKET_ALREADY_EXISTS,
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::STORAGE_BUCKET_NOT_FOUND => [
@@ -479,7 +519,7 @@ return [
],
Exception::COLLECTION_ALREADY_EXISTS => [
'name' => Exception::COLLECTION_ALREADY_EXISTS,
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::COLLECTION_LIMIT_EXCEEDED => [
@@ -511,7 +551,7 @@ return [
],
Exception::DOCUMENT_ALREADY_EXISTS => [
'name' => Exception::DOCUMENT_ALREADY_EXISTS,
'description' => 'Document with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::DOCUMENT_UPDATE_CONFLICT => [
@@ -553,7 +593,7 @@ return [
],
Exception::ATTRIBUTE_ALREADY_EXISTS => [
'name' => Exception::ATTRIBUTE_ALREADY_EXISTS,
'description' => 'Attribute with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.',
'code' => 409,
],
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
@@ -585,7 +625,7 @@ return [
],
Exception::INDEX_ALREADY_EXISTS => [
'name' => Exception::INDEX_ALREADY_EXISTS,
'description' => 'Index with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Index with the requested key already exists. Try again with a different key.',
'code' => 409,
],
Exception::INDEX_INVALID => [
@@ -602,7 +642,7 @@ return [
],
Exception::PROJECT_ALREADY_EXISTS => [
'name' => Exception::PROJECT_ALREADY_EXISTS,
'description' => 'Project with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Project with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::PROJECT_UNKNOWN => [
@@ -732,4 +772,90 @@ return [
'description' => 'Migration is already in progress. You can check the status of the migration in your Appwrite Console\'s "Settings" > "Migrations".',
'code' => 409,
],
/** Realtime */
Exception::REALTIME_MESSAGE_FORMAT_INVALID => [
'name' => Exception::REALTIME_MESSAGE_FORMAT_INVALID,
'description' => 'Message format is not valid.',
'code' => 1003,
],
Exception::REALTIME_POLICY_VIOLATION => [
'name' => Exception::REALTIME_POLICY_VIOLATION,
'description' => 'Policy violation.',
'code' => 1008,
],
Exception::REALTIME_TOO_MANY_MESSAGES => [
'name' => Exception::REALTIME_TOO_MANY_MESSAGES,
'description' => 'Too many messages.',
'code' => 1013,
],
Exception::MIGRATION_PROVIDER_ERROR => [
'name' => Exception::MIGRATION_PROVIDER_ERROR,
'description' => 'An error occurred on the provider\'s side. Please try again later.',
'code' => 400,
],
/** Provider Errors */
Exception::PROVIDER_NOT_FOUND => [
'name' => Exception::PROVIDER_NOT_FOUND,
'description' => 'Provider with the requested ID could not be found.',
'code' => 404,
],
Exception::PROVIDER_ALREADY_EXISTS => [
'name' => Exception::PROVIDER_ALREADY_EXISTS,
'description' => 'Provider with the requested ID already exists.',
'code' => 409,
],
Exception::PROVIDER_INCORRECT_TYPE => [
'name' => Exception::PROVIDER_INCORRECT_TYPE,
'description' => 'Provider with the requested ID is of incorrect type: ',
'code' => 400,
],
/** Topic Errors */
Exception::TOPIC_NOT_FOUND => [
'name' => Exception::TOPIC_NOT_FOUND,
'description' => 'Topic with the request ID could not be found.',
'code' => 404,
],
Exception::TOPIC_ALREADY_EXISTS => [
'name' => Exception::TOPIC_ALREADY_EXISTS,
'description' => 'Topic with the request ID already exists.',
'code' => 409,
],
/** Subscriber Errors */
Exception::SUBSCRIBER_NOT_FOUND => [
'name' => Exception::SUBSCRIBER_NOT_FOUND,
'description' => 'Subscriber with the request ID could not be found.',
'code' => 404,
],
Exception::SUBSCRIBER_ALREADY_EXISTS => [
'name' => Exception::SUBSCRIBER_ALREADY_EXISTS,
'description' => 'Subscriber with the request ID already exists.',
'code' => 409,
],
/** Message Errors */
Exception::MESSAGE_NOT_FOUND => [
'name' => Exception::MESSAGE_NOT_FOUND,
'description' => 'Message with the requested ID could not be found.',
'code' => 404,
],
Exception::MESSAGE_MISSING_TARGET => [
'name' => Exception::MESSAGE_MISSING_TARGET,
'description' => 'Message with the requested ID is missing a target (Topics or Users or Targets).',
'code' => 400,
],
Exception::MESSAGE_ALREADY_SENT => [
'name' => Exception::MESSAGE_ALREADY_SENT,
'description' => 'Message with the requested ID has already been sent.',
'code' => 400,
],
Exception::MESSAGE_ALREADY_SCHEDULED => [
'name' => Exception::MESSAGE_ALREADY_SCHEDULED,
'description' => 'Message with the requested ID has already been scheduled for delivery.',
'code' => 400,
],
];
+64
View File
@@ -44,6 +44,20 @@ return [
'$description' => 'This event triggers when a verification token for a user is validated.'
],
],
'targets' => [
'$model' => Response::MODEL_TARGET,
'$resource' => true,
'$description' => 'This event triggers on any user\'s target event.',
'create' => [
'$description' => 'This event triggers when a user\'s target is created.',
],
'update' => [
'$description' => 'This event triggers when a user\'s target is updated.',
],
'delete' => [
'$description' => 'This event triggers when a user\'s target is deleted.',
],
],
'create' => [
'$description' => 'This event triggers when a user is created.'
],
@@ -237,6 +251,56 @@ return [
'$description' => 'This event triggers when a function is updated.',
]
],
'messages' => [
'$model' => Response::MODEL_MESSAGE,
'$resource' => true,
'$description' => 'This event triggers on any messaging event.',
'create' => [
'$description' => 'This event triggers when a message is created.',
],
'update' => [
'$description' => 'This event triggers when a message is updated.',
],
],
'topics' => [
'$model' => Response::MODEL_TOPIC,
'$resource' => true,
'$description' => 'This event triggers on any topic event.',
'create' => [
'$description' => 'This event triggers when a topic is created.',
],
'update' => [
'$description' => 'This event triggers when a topic is updated.',
],
'delete' => [
'$description' => 'This event triggers when a topic is deleted.'
],
'subscribers' => [
'$model' => Response::MODEL_SUBSCRIBER,
'$resource' => true,
'$description' => 'This event triggers on any subscriber event.',
'create' => [
'$description' => 'This event triggers when a subscriber is created.',
],
'delete' => [
'$description' => 'This event triggers when a subscriber is deleted.'
],
],
],
'providers' => [
'$model' => Response::MODEL_PROVIDER,
'$resource' => true,
'$description' => 'This event triggers on any provider event.',
'create' => [
'$description' => 'This event triggers when a provider is created.',
],
'update' => [
'$description' => 'This event triggers when a provider is updated.',
],
'delete' => [
'$description' => 'This event triggers when a provider is deleted.'
],
],
'rules' => [
'$model' => Response::MODEL_PROXY_RULE,
'$resource' => true,
@@ -6,6 +6,10 @@
<a href="{{redirect}}" target="_blank">{{redirect}}</a>
<b>{{code}}</b>
<i>{{securityPhrase}}</i>
<p>{{footer}}</p>
<br>
+7
View File
@@ -12,9 +12,16 @@
"emails.magicSession.subject": "Login",
"emails.magicSession.hello": "Hey,",
"emails.magicSession.body": "Follow this link to login.",
"emails.magicSession.codeBody": "Enter this code to login.",
"emails.magicSession.footer": "If you didnt ask to login using this email, you can ignore this message.",
"emails.magicSession.thanks": "Thanks",
"emails.magicSession.signature": "{{project}} team",
"emails.authNotify.subject": "New session alert for {{project}}",
"emails.authNotify.hello": "Hey {{user}},",
"emails.authNotify.body": "We're writing to inform you that a new session has been initiated on your {{project}} account on {{date}} at {{time}}. <br><hr><br>Here are the details: <br>Device: {{device}}<br>Location: {{country}}<br>IP Address: {{ip}} <br><hr><br>If you did not initiate this session, we strongly recommend that you review your account settings.",
"emails.authNotify.footer": "If you recognize this activity, no further action is required.",
"emails.authNotify.thanks": "Thanks",
"emails.authNotify.signature": "{{project}} team",
"emails.recovery.subject": "Password Reset",
"emails.recovery.hello": "Hello {{user}}",
"emails.recovery.body": "Follow this link to reset your {{project}} password.",
@@ -152,6 +152,16 @@ return [
'beta' => false,
'mock' => false,
],
'supabase' => [
'name' => 'Supabase',
'developers' => 'https://supabase.com/docs',
'icon' => 'icon-supabase',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'gitlab' => [
'name' => 'GitLab',
'developers' => 'https://docs.gitlab.com/ee/api/',
+14
View File
@@ -21,6 +21,10 @@ $member = [
'avatars.read',
'execution.read',
'execution.write',
'targets.read',
'targets.write',
'subscribers.write',
'subscribers.read',
'assistant.read',
];
@@ -60,6 +64,16 @@ $admins = [
'migrations.write',
'vcs.read',
'vcs.write',
'targets.read',
'targets.write',
'providers.write',
'providers.read',
'messages.write',
'messages.read',
'topics.write',
'topics.read',
'subscribers.write',
'subscribers.read'
];
return [
+30
View File
@@ -76,6 +76,36 @@ return [ // List of publicly visible scopes
'health.read' => [
'description' => 'Access to read your project\'s health status',
],
'providers.read' => [
'description' => 'Access to read your project\'s providers',
],
'providers.write' => [
'description' => 'Access to create, update, and delete your project\'s providers',
],
'messages.read' => [
'description' => 'Access to read your project\'s messages',
],
'messages.write' => [
'description' => 'Access to create, update, and delete your project\'s messages',
],
'topics.read' => [
'description' => 'Access to read your project\'s topics',
],
'topics.write' => [
'description' => 'Access to create, update, and delete your project\'s topics',
],
'subscribers.read' => [
'description' => 'Access to read your project\'s subscribers',
],
'subscribers.write' => [
'description' => 'Access to create, update, and delete your project\'s subscribers',
],
'targets.read' => [
'description' => 'Access to read your project\'s targets',
],
'targets.write' => [
'description' => 'Access to create, update, and delete your project\'s targets',
],
'rules.read' => [
'description' => 'Access to read your project\'s proxy rules',
],
+16 -3
View File
@@ -170,7 +170,7 @@ return [
'docs' => false,
'docsUrl' => '',
'tests' => false,
'optional' => true,
'optional' => false,
'icon' => '',
],
'functions' => [
@@ -196,7 +196,7 @@ return [
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/proxy',
'tests' => false,
'optional' => true,
'optional' => false,
'icon' => '/images/services/proxy.png',
],
'mock' => [
@@ -248,7 +248,20 @@ return [
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/migrations',
'tests' => true,
'optional' => true,
'optional' => false,
'icon' => '/images/services/migrations.png',
],
'messaging' => [
'key' => 'messaging',
'name' => 'Messaging',
'subtitle' => 'The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.).',
'description' => '/docs/services/messaging.md',
'controller' => 'api/messaging.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/server/messaging',
'tests' => true,
'optional' => true,
'icon' => '/images/services/messaging.png',
]
];
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -440,7 +440,7 @@ return [
'variables' => [
[
'name' => '_APP_SMS_PROVIDER',
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, text-magic, telesign, msg91, and vonage.",
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, Textmagic, telesign, msg91, and vonage.",
'introduction' => '0.15.0',
'default' => '',
'required' => false,
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -84,8 +84,8 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
$accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry');
$refreshToken = $gitHubSession->getAttribute('providerRefreshToken');
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
+4 -4
View File
@@ -1571,7 +1571,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in ISO 8601 format. Cannot be set when attribute is required.', true)
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true)
->inject('response')
->inject('dbForProject')
@@ -3632,7 +3632,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
App::get('/v1/databases/usage')
->desc('Get usage stats for the database')
->groups(['api', 'database'])
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'databases')
@@ -3750,7 +3750,7 @@ App::get('/v1/databases/usage')
App::get('/v1/databases/:databaseId/usage')
->desc('Get usage stats for the database')
->groups(['api', 'database'])
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'databases')
@@ -3860,7 +3860,7 @@ App::get('/v1/databases/:databaseId/usage')
App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
->alias('/v1/database/:collectionId/usage', ['databaseId' => 'default'])
->desc('Get usage stats for a collection')
->groups(['api', 'database'])
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'databases')
+21 -10
View File
@@ -46,6 +46,7 @@ use Utopia\Validator\Boolean;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use MaxMind\Db\Reader;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
include_once __DIR__ . '/../shared/api.php';
@@ -58,7 +59,14 @@ $redeployVcs = function (Request $request, Document $function, Document $project
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$providerBranch = $function->getAttribute('providerBranch', 'main');
$authorUrl = "https://github.com/$owner";
$repositoryUrl = "https://github.com/$owner/$repositoryName";
@@ -234,12 +242,16 @@ App::post('/v1/functions')
// Git connect logic
if (!empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
@@ -287,9 +299,8 @@ App::post('/v1/functions')
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME)
;
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
$ruleCreate
->setProject($project)
@@ -870,8 +881,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->setContentType('application/gzip')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"')
;
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
$size = $deviceFunctions->getFileSize($path);
$rangeHeader = $request->getHeader('range');
@@ -899,7 +909,6 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
}
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $deviceFunctions->getFileSize($path));
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFunctions->read(
@@ -1653,6 +1662,8 @@ App::post('/v1/functions/:functionId/executions')
->setJWT($jwt)
->setProject($project)
->setUser($user)
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->trigger();
return $response
+122 -35
View File
@@ -14,6 +14,7 @@ use Utopia\Registry\Registry;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
App::get('/v1/health')
@@ -87,22 +88,18 @@ App::get('/v1/health/db')
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
$failure[] = $database;
}
} catch (\Throwable $th) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
$failure[] = $database;
}
}
}
if (!empty($failure)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failure));
}
$response->dynamic(new Document([
'statuses' => $output,
'total' => count($output),
@@ -348,11 +345,20 @@ App::get('/v1/health/queue/webhooks')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::WEBHOOK_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/logs')
@@ -366,11 +372,20 @@ App::get('/v1/health/queue/logs')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::AUDITS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/certificates')
@@ -384,11 +399,20 @@ App::get('/v1/health/queue/certificates')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::CERTIFICATES_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/builds')
@@ -402,11 +426,20 @@ App::get('/v1/health/queue/builds')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::BUILDS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/databases')
@@ -421,11 +454,20 @@ App::get('/v1/health/queue/databases')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->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('queue')
->inject('response')
->action(function (string $name, Connection $queue, Response $response) {
->action(function (string $name, int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client($name, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/deletes')
@@ -439,11 +481,20 @@ App::get('/v1/health/queue/deletes')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::DELETE_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/mails')
@@ -457,11 +508,20 @@ App::get('/v1/health/queue/mails')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MAILS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/messaging')
@@ -475,11 +535,20 @@ App::get('/v1/health/queue/messaging')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MESSAGING_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/migrations')
@@ -493,11 +562,20 @@ App::get('/v1/health/queue/migrations')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MIGRATIONS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/functions')
@@ -511,11 +589,20 @@ App::get('/v1/health/queue/functions')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::FUNCTIONS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/storage/local')
@@ -536,10 +623,10 @@ App::get('/v1/health/storage/local')
foreach (
[
'Uploads' => APP_STORAGE_UPLOADS,
'Cache' => APP_STORAGE_CACHE,
'Config' => APP_STORAGE_CONFIG,
'Certs' => APP_STORAGE_CERTIFICATES
'Uploads' => APP_STORAGE_UPLOADS,
'Cache' => APP_STORAGE_CACHE,
'Config' => APP_STORAGE_CONFIG,
'Certs' => APP_STORAGE_CERTIFICATES
] as $key => $volume
) {
$device = new Local($volume);
@@ -601,7 +688,7 @@ App::get('/v1/health/anti-virus')
});
App::get('/v1/health/stats') // Currently only used internally
->desc('Get system stats')
->desc('Get system stats')
->groups(['api', 'health'])
->label('scope', 'root')
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
File diff suppressed because it is too large Load Diff
+91 -27
View File
@@ -208,6 +208,16 @@ App::post('/v1/migrations/firebase')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$serviceAccountData = json_decode($serviceAccount, true);
if (empty($serviceAccountData)) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
if (!isset($serviceAccountData['project_id']) || !isset($serviceAccountData['client_email']) || !isset($serviceAccountData['private_key'])) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
@@ -449,15 +459,26 @@ App::get('/v1/migrations/appwrite/report')
->inject('project')
->inject('user')
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) {
try {
$appwrite = new Appwrite($projectID, $endpoint, $key);
$appwrite = new Appwrite($projectID, $endpoint, $key);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($appwrite->report($resources)), Response::MODEL_MIGRATION_REPORT);
try {
$report = $appwrite->report($resources);
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/firebase/report')
@@ -475,15 +496,36 @@ App::get('/v1/migrations/firebase/report')
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
->action(function (array $resources, string $serviceAccount, Response $response) {
try {
$firebase = new Firebase(json_decode($serviceAccount, true));
$serviceAccount = json_decode($serviceAccount, true);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($firebase->report($resources)), Response::MODEL_MIGRATION_REPORT);
} catch (\Exception $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
if (empty($serviceAccount)) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
if (!isset($serviceAccount['project_id']) || !isset($serviceAccount['client_email']) || !isset($serviceAccount['private_key'])) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
$firebase = new Firebase($serviceAccount);
try {
$report = $firebase->report($resources);
} catch (\Throwable $e) {
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/firebase/report/oauth')
@@ -869,15 +911,26 @@ App::get('/v1/migrations/supabase/report')
->inject('response')
->inject('dbForProject')
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response) {
try {
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($supabase->report($resources)), Response::MODEL_MIGRATION_REPORT);
} catch (\Exception $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
try {
$report = $supabase->report($resources);
} catch (\Throwable $e) {
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/nhost/report')
@@ -901,15 +954,26 @@ App::get('/v1/migrations/nhost/report')
->param('port', 5432, new Integer(true), 'Source\'s Database Port.', true)
->inject('response')
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response) {
try {
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($nhost->report($resources)), Response::MODEL_MIGRATION_REPORT);
} catch (\Exception $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
try {
$report = $nhost->report($resources);
} catch (\Throwable $e) {
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::patch('/v1/migrations/:migrationId')
+85 -18
View File
@@ -85,19 +85,19 @@ App::post('/v1/projects')
}
$auth = Config::getParam('auth', []);
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordAi' => false, 'renewal' => false, 'passwordDictionary' => false, 'passwordDictionaryLength' => '10k', 'notify' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
foreach ($auth as $index => $method) {
$auths[$method['key'] ?? ''] = true;
}
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
$backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '04:00'];
$backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '05:00'];
$backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00'];
$databases = Config::getParam('pools-database', []);
@@ -123,7 +123,7 @@ App::post('/v1/projects')
$databaseOverride = App::getEnv('_APP_DATABASE_OVERRIDE', null);
$index = array_search($databaseOverride, $databases);
if ($index) {
if ($index !== false) {
$database = $databases[$index];
} else {
$database = $databases[array_rand($databases)];
@@ -159,7 +159,7 @@ App::post('/v1/projects')
'legalTaxId' => ID::custom($legalTaxId),
'services' => new stdClass(),
'platforms' => null,
'authProviders' => [],
'oAuthProviders' => [],
'webhooks' => null,
'keys' => null,
'auths' => $auths,
@@ -598,7 +598,7 @@ App::patch('/v1/projects/:projectId/oauth2')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'Provider Name')
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
@@ -612,7 +612,7 @@ App::patch('/v1/projects/:projectId/oauth2')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$providers = $project->getAttribute('authProviders', []);
$providers = $project->getAttribute('oAuthProviders', []);
if ($appId !== null) {
$providers[$provider . 'Appid'] = $appId;
@@ -626,7 +626,7 @@ App::patch('/v1/projects/:projectId/oauth2')
$providers[$provider . 'Enabled'] = $enabled;
}
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('authProviders', $providers));
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('oAuthProviders', $providers));
$response->dynamic($project, Response::MODEL_PROJECT);
});
@@ -674,9 +674,10 @@ App::patch('/v1/projects/:projectId/auth/duration')
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('duration', 31536000, new Range(0, 31536000), 'Project session length in seconds. Max length: 31536000 seconds.')
->param('renewal', false, new Boolean(), 'Automatic session refresh. If enabled, sessions are automatically extended to session duration on every request.', true)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) {
->action(function (string $projectId, int $duration, bool $renewal, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
@@ -686,6 +687,7 @@ App::patch('/v1/projects/:projectId/auth/duration')
$auths = $project->getAttribute('auths', []);
$auths['duration'] = $duration;
$auths['renewal'] = $renewal;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
@@ -759,7 +761,7 @@ App::patch('/v1/projects/:projectId/auth/password-history')
});
App::patch('/v1/projects/:projectId/auth/password-dictionary')
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password')
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dictionary check for user password')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -770,6 +772,39 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.')
->param('length', '10k', new WhiteList(['10k', '100k', '1m', '10m'], true), 'Set the length of the password dictionary to use', true)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, string $length, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['passwordDictionary'] = $enabled;
$auths['passwordDictionaryLength'] = $length;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/password-ai')
->desc('Update authentication password ai status.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthPasswordAi')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against AI opinion. Default is false.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
@@ -781,7 +816,40 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
}
$auths = $project->getAttribute('auths', []);
$auths['passwordDictionary'] = $enabled;
$auths['passwordAi'] = $enabled;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/notify')
->desc('Update authentication notify status. Enable or disable notifications for new session creations.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthNotify')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('enabled', false, new Boolean(false), 'Set whether or not to enable notifications for new session creations. Default is false.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
if (!empty($projectId)) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
}
$auths = $project->getAttribute('auths', []);
$auths['notify'] = $enabled;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
@@ -1151,7 +1219,7 @@ App::post('/v1/projects/:projectId/keys')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
@@ -1268,7 +1336,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
@@ -1703,7 +1771,6 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
->setParam('{{user}}', '')
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'))
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
+5 -1
View File
@@ -87,7 +87,11 @@ App::post('/v1/proxy/rules')
$resourceInternalId = $function->getInternalId();
}
$domain = new Domain($domain);
try {
$domain = new Domain($domain);
} catch (\Exception) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
}
$ruleId = ID::unique();
$rule = new Document([
-2
View File
@@ -1109,7 +1109,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFiles->read(
@@ -1262,7 +1261,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$size = $deviceFiles->getFileSize($path);
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFiles->read(
+23 -14
View File
@@ -6,7 +6,7 @@ use Appwrite\Detector\Detector;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Mail;
use Appwrite\Event\Phone as EventPhone;
use Appwrite\Event\Messaging;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\Host;
@@ -388,7 +388,7 @@ App::post('/v1/teams/:teamId/memberships')
->inject('queueForMails')
->inject('queueForMessaging')
->inject('queueForEvents')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, EventPhone $queueForMessaging, Event $queueForEvents) {
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) {
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@@ -555,7 +555,12 @@ App::post('/v1/teams/:teamId/memberships')
$customTemplate = $project->getAttribute('templates', [])['email.invitation-' . $locale->default] ?? [];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message->setParam('{{body}}', $body);
$message
->setParam('{{body}}', $body)
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
@@ -606,16 +611,7 @@ App::post('/v1/teams/:teamId/memberships')
$emailVariables = [
'owner' => $user->getAttribute('name'),
'subject' => $subject,
'hello' => $locale->getText("emails.invitation.hello"),
'body' => $body,
'footer' => $locale->getText("emails.invitation.footer"),
'thanks' => $locale->getText("emails.invitation.thanks"),
'signature' => $locale->getText("emails.invitation.signature"),
'direction' => $locale->getText('settings.direction'),
'bg-body' => '#f7f7f7',
'bg-content' => '#ffffff',
'text-content' => '#000000',
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
'user' => $user->getAttribute('name'),
'team' => $team->getAttribute('name'),
@@ -632,6 +628,10 @@ App::post('/v1/teams/:teamId/memberships')
->trigger()
;
} elseif (!empty($phone)) {
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
$customTemplate = $project->getAttribute('templates', [])['sms.invitation-' . $locale->default] ?? [];
@@ -642,9 +642,18 @@ App::post('/v1/teams/:teamId/memberships')
$message = $message->setParam('{{token}}', $url);
$message = $message->render();
$messageDoc = new Document([
'$id' => ID::unique(),
'data' => [
'content' => $message,
],
]);
$queueForMessaging
->setRecipient($phone)
->setMessage($message)
->setMessage($messageDoc)
->setRecipients([$phone])
->setProviderType('SMS')
->setProject($project)
->trigger();
}
}
+450 -7
View File
@@ -2,6 +2,7 @@
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\Password;
use Appwrite\Auth\Validator\PasswordAi;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
use Appwrite\Event\Delete;
@@ -9,6 +10,7 @@ use Appwrite\Event\Event;
use Appwrite\Network\Validator\Email;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Users;
use Utopia\Database\Validator\Query\Limit;
@@ -70,6 +72,13 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
}
}
if ($project->getAttribute('auths', [])['passwordAi'] ?? false) {
$passwordAiValidator = new PasswordAi();
if (!$passwordAiValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_AI);
}
}
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$user = $dbForProject->createDocument('users', new Document([
'$id' => $userId,
@@ -85,7 +94,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'status' => true,
'labels' => [],
'password' => $password,
'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password],
'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password],
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
@@ -98,6 +107,42 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'memberships' => null,
'search' => implode(' ', [$userId, $email, $phone, $name]),
]));
if ($email) {
try {
$target = $dbForProject->createDocument('targets', new Document([
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'email',
'identifier' => $email,
]));
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
} catch (Duplicate) {
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
}
}
if ($phone) {
try {
$target = $dbForProject->createDocument('targets', new Document([
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'sms',
'identifier' => $phone,
]));
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
} catch (Duplicate) {
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
}
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
@@ -125,13 +170,35 @@ App::post('/v1/users')
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', null, new Email(), 'User email.', true)
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
->param('password', '', fn ($project, $passwordsDictionary10k) => new PasswordDictionary($passwordsDictionary10k, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary10k'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
->inject('passwordsDictionary10k')
->inject('passwordsDictionary100k')
->inject('passwordsDictionary1M')
->inject('passwordsDictionary10M')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, array $passwordsDictionary10k, array $passwordsDictionary100k, array $passwordsDictionary1M, array $passwordsDictionary10M) {
if ($project->getAttribute('auths', [])['passwordDictionary'] ?? false) {
$passwordDictionaryLength = $project->getAttribute('auths', [])['passwordDictionaryLength'] ?? '10k';
if ($passwordDictionaryLength == '10k') {
$passwordDictionary = $passwordsDictionary10k;
} elseif ($passwordDictionaryLength == '100k') {
$passwordDictionary = $passwordsDictionary100k;
} elseif ($passwordDictionaryLength == '1m') {
$passwordDictionary = $passwordsDictionary1M;
} elseif ($passwordDictionaryLength == '10m') {
$passwordDictionary = $passwordsDictionary10M;
} else {
throw new Exception('Password dictionary length is not supported');
}
$passwordDictionaryValidator = new PasswordDictionary($passwordDictionary, true);
if (!$passwordDictionaryValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_DICTIONARY);
}
}
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents);
$response
@@ -379,6 +446,98 @@ App::post('/v1/users/scrypt-modified')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/:userId/targets')
->desc('Create User Target')
->groups(['api', 'users'])
->label('audits.event', 'target.create')
->label('audits.resource', 'target/response.$id')
->label('event', 'users.[userId].targets.[targetId].create')
->label('scope', 'targets.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createTarget')
->label('sdk.description', '/docs/references/users/create-target.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TARGET)
->param('targetId', '', new CustomId(), 'Target 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('userId', '', new UID(), 'User ID.')
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
->inject('dbForProject')
->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
$provider = new Document();
if ($providerType === MESSAGE_TYPE_PUSH) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
throw new Exception(Exception::PROVIDER_NOT_FOUND);
}
}
switch ($providerType) {
case 'email':
$validator = new Email();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
break;
case MESSAGE_TYPE_SMS:
$validator = new Phone();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_PHONE);
}
break;
case MESSAGE_TYPE_PUSH:
break;
default:
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
}
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$target = $dbForProject->getDocument('targets', $targetId);
if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
try {
$target = $dbForProject->createDocument('targets', new Document([
'$id' => $targetId,
'providerId' => $providerId ?? null,
'providerInternalId' => $provider->getInternalId() ?? null,
'providerType' => $providerType,
'userId' => $userId,
'userInternalId' => $user->getInternalId(),
'identifier' => $identifier,
'name' => ($name !== '') ? $name : null,
]));
} catch (Duplicate) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
->setParam('targetId', $target->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($target, Response::MODEL_TARGET);
});
App::get('/v1/users')
->desc('List users')
->groups(['api', 'users'])
@@ -482,6 +641,38 @@ App::get('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
App::get('/v1/users/:userId/targets/:targetId')
->desc('Get User Target')
->groups(['api', 'users'])
->label('scope', 'targets.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
->label('sdk.method', 'getTarget')
->label('sdk.description', '/docs/references/users/get-user-target.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TARGET)
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $targetId, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$target = $user->find('$id', $targetId, 'targets');
if (empty($target)) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
$response->dynamic($target, Response::MODEL_TARGET);
});
App::get('/v1/users/:userId/sessions')
->desc('List user sessions')
->groups(['api', 'users'])
@@ -646,6 +837,53 @@ App::get('/v1/users/:userId/logs')
]), Response::MODEL_LOG_LIST);
});
App::get('/v1/users/:userId/targets')
->desc('List User Targets')
->groups(['api', 'users'])
->label('scope', 'targets.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
->label('sdk.method', 'listTargets')
->label('sdk.description', '/docs/references/users/list-user-targets.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TARGET_LIST)
->param('userId', '', new UID(), 'User ID.')
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Users::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->action(function (string $userId, array $queries, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$queries[] = Query::equal('userId', [$userId]);
// Get cursor document if there was a cursor query
$cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]);
$cursor = reset($cursor);
if ($cursor) {
$targetId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('targets', $targetId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Target '{$targetId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$response->dynamic(new Document([
'targets' => $dbForProject->find('targets', $queries),
'total' => $dbForProject->count('targets', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_TARGET_LIST);
});
App::get('/v1/users/identities')
->desc('List Identities')
->groups(['api', 'users'])
@@ -861,12 +1099,16 @@ App::patch('/v1/users/:userId/password')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User ID.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
->param('password', '', fn ($project, $passwordsDictionary10k) => new PasswordDictionary($passwordsDictionary10k, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary10k'])
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
->inject('passwordsDictionary10k')
->inject('passwordsDictionary100k')
->inject('passwordsDictionary1M')
->inject('passwordsDictionary10M')
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, array $passwordsDictionary10k, array $passwordsDictionary100k, array $passwordsDictionary1M, array $passwordsDictionary10M) {
$user = $dbForProject->getDocument('users', $userId);
@@ -880,6 +1122,32 @@ App::patch('/v1/users/:userId/password')
throw new Exception(Exception::USER_PASSWORD_PERSONAL_DATA);
}
}
if ($project->getAttribute('auths', [])['passwordAi'] ?? false) {
$passwordAiValidator = new PasswordAi();
if (!$passwordAiValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_AI);
}
}
if ($project->getAttribute('auths', [])['passwordDictionary'] ?? false) {
$passwordDictionaryLength = $project->getAttribute('auths', [])['passwordDictionaryLength'] ?? '10k';
if ($passwordDictionaryLength == '10k') {
$passwordDictionary = $passwordsDictionary10k;
} elseif ($passwordDictionaryLength == '100k') {
$passwordDictionary = $passwordsDictionary100k;
} elseif ($passwordDictionaryLength == '1m') {
$passwordDictionary = $passwordsDictionary1M;
} elseif ($passwordDictionaryLength == '10m') {
$passwordDictionary = $passwordsDictionary10M;
} else {
throw new Exception('Password dictionary length is not supported');
}
$passwordDictionaryValidator = new PasswordDictionary($passwordDictionary, true);
if (!$passwordDictionaryValidator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_DICTIONARY);
}
}
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
@@ -949,6 +1217,16 @@ App::patch('/v1/users/:userId/email')
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
$target = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
$oldEmail = $user->getAttribute('email');
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false)
@@ -957,6 +1235,15 @@ App::patch('/v1/users/:userId/email')
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email));
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
@@ -994,13 +1281,32 @@ App::patch('/v1/users/:userId/phone')
throw new Exception(Exception::USER_NOT_FOUND);
}
$oldPhone = $user->getAttribute('phone');
$user
->setAttribute('phone', $number)
->setAttribute('phoneVerification', false)
;
$target = $dbForProject->findOne('targets', [
Query::equal('identifier', [$number]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $number));
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
}
@@ -1080,6 +1386,96 @@ App::patch('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
App::patch('/v1/users/:userId/targets/:targetId')
->desc('Update User target')
->groups(['api', 'users'])
->label('audits.event', 'target.update')
->label('audits.resource', 'target/{response.$id}')
->label('event', 'users.[userId].targets.[targetId].update')
->label('scope', 'targets.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateTarget')
->label('sdk.description', '/docs/references/users/update-target.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TARGET)
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true)
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $targetId, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$target = $dbForProject->getDocument('targets', $targetId);
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($user->getId() !== $target->getAttribute('userId')) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($identifier) {
$providerType = $target->getAttribute('providerType');
switch ($providerType) {
case 'email':
$validator = new Email();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
break;
case MESSAGE_TYPE_SMS:
$validator = new Phone();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_PHONE);
}
break;
case MESSAGE_TYPE_PUSH:
break;
default:
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
}
$target->setAttribute('identifier', $identifier);
}
if ($providerId) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
throw new Exception(Exception::PROVIDER_NOT_FOUND);
}
$target->setAttribute('providerId', $provider->getId());
$target->setAttribute('providerInternalId', $provider->getInternalId());
}
if ($name) {
$target->setAttribute('name', $name);
}
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
$dbForProject->deleteCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
->setParam('targetId', $target->getId());
$response
->dynamic($target, Response::MODEL_TARGET);
});
App::delete('/v1/users/:userId/sessions/:sessionId')
->desc('Delete user session')
->groups(['api', 'users'])
@@ -1127,7 +1523,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
App::delete('/v1/users/:userId/sessions')
->desc('Delete user sessions')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('event', 'users.[userId].sessions.delete')
->label('scope', 'users.write')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
@@ -1210,6 +1606,53 @@ App::delete('/v1/users/:userId')
$response->noContent();
});
App::delete('/v1/users/:userId/targets/:targetId')
->desc('Delete user target')
->groups(['api', 'users'])
->label('audits.event', 'target.delete')
->label('audits.resource', 'target/{request.$targetId}')
->label('event', 'users.[userId].targets.[targetId].delete')
->label('scope', 'targets.write')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteTarget')
->label('sdk.description', '/docs/references/users/delete-target.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_NONE)
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->inject('queueForEvents')
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $targetId, Event $queueForEvents, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$target = $dbForProject->getDocument('targets', $targetId);
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
if ($user->getId() !== $target->getAttribute('userId')) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
$dbForProject->deleteDocument('targets', $target->getId());
$dbForProject->deleteCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
->setParam('targetId', $target->getId());
$response->noContent();
});
App::delete('/v1/users/identities/:identityId')
->desc('Delete Identity')
->groups(['api', 'users'])
@@ -1251,7 +1694,7 @@ App::get('/v1/users/usage')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('oAuthProviders', [])))), true), 'Provider Name.', true)
->inject('response')
->inject('dbForProject')
->inject('register')
+67 -30
View File
@@ -22,7 +22,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Detector\Adapter\Bun;
use Utopia\Detector\Adapter\CPP;
use Utopia\Detector\Adapter\Dart;
@@ -36,6 +35,7 @@ use Utopia\Detector\Adapter\Ruby;
use Utopia\Detector\Adapter\Swift;
use Utopia\Detector\Detector;
use Utopia\Validator\Boolean;
use Utopia\VCS\Exception\RepositoryNotFound;
use function Swoole\Coroutine\batch;
@@ -66,7 +66,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
@@ -154,7 +161,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$message = 'Authorization required for external contributor.';
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$owner = $github->getOwnerName($providerInstallationId);
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'failure', $message, $authorizeUrl, $name);
continue;
@@ -206,7 +220,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$message = 'Starting...';
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$owner = $github->getOwnerName($providerInstallationId);
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/functions/function-$functionId";
@@ -273,7 +294,7 @@ App::get('/v1/vcs/github/callback')
->label('scope', 'public')
->label('error', __DIR__ . '/../../views/general/error.phtml')
->param('installation_id', '', new Text(256, 0), 'GitHub installation ID', true)
->param('setup_action', '', new Text(256, 0), 'GitHub setup actuon type', true)
->param('setup_action', '', new Text(256, 0), 'GitHub setup action type', true)
->param('state', '', new Text(2048), 'GitHub state. Contains info sent when starting authorization flow.', true)
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
->inject('gitHub')
@@ -458,9 +479,12 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -720,9 +744,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -766,9 +793,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -827,10 +857,10 @@ App::post('/v1/vcs/github/events')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
//find functionId from functions table
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::limit(100),
]);
]));
// create new deployment only on push and not when branch is created
if (!$providerBranchCreated) {
@@ -847,13 +877,13 @@ App::post('/v1/vcs/github/events')
]);
foreach ($installations as $installation) {
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('installationInternalId', [$installation->getInternalId()]),
Query::limit(1000)
]);
]));
foreach ($repositories as $repository) {
$dbForConsole->deleteDocument('repositories', $repository->getId());
Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
}
$dbForConsole->deleteDocument('installations', $installation->getId());
@@ -885,10 +915,10 @@ App::post('/v1/vcs/github/events')
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]);
]));
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
} elseif ($parsedPayload["action"] == "closed") {
@@ -899,10 +929,10 @@ App::post('/v1/vcs/github/events')
$external = $parsedPayload["external"] ?? true;
if ($external) {
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]);
]));
foreach ($repositories as $repository) {
$providerPullRequestIds = $repository->getAttribute('providerPullRequestIds', []);
@@ -1016,8 +1046,8 @@ App::delete('/v1/vcs/installations/:installationId')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('deletes')
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $deletes) {
->inject('queueForDeletes')
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes) {
$installation = $dbForConsole->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
@@ -1028,7 +1058,7 @@ App::delete('/v1/vcs/installations/:installationId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB');
}
$deletes
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($installation);
@@ -1062,9 +1092,9 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$repository = $dbForConsole->getDocument('repositories', $repositoryId, [
$repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
]));
if ($repository->isEmpty()) {
throw new Exception(Exception::REPOSITORY_NOT_FOUND);
@@ -1079,7 +1109,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
// TODO: Delete from array when PR is closed
$repository = $dbForConsole->updateDocument('repositories', $repository->getId(), $repository);
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
@@ -1090,7 +1120,14 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
$owner = $github->getOwnerName($providerInstallationId);
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$pullRequestResponse = $github->getPullRequest($owner, $repositoryName, $providerPullRequestId);
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
+3 -1
View File
@@ -117,12 +117,14 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
$path .= '?' . $query;
}
$requestHeaders = $request->getHeaders();
$body = \json_encode([
'async' => false,
'body' => $swooleRequest->getContent() ?? '',
'method' => $swooleRequest->server['request_method'],
'path' => $path,
'headers' => $swooleRequest->header
'headers' => $requestHeaders
]);
$headers = [
+63 -496
View File
@@ -9,506 +9,13 @@ use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
use Utopia\Storage\Validator\File;
use Utopia\Validator\WhiteList;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Validator\Nullable;
App::get('/v1/mock/tests/foo')
->desc('Get Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::post('/v1/mock/tests/foo')
->desc('Post Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::patch('/v1/mock/tests/foo')
->desc('Patch Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a patch request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::put('/v1/mock/tests/foo')
->desc('Put Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::delete('/v1/mock/tests/foo')
->desc('Delete Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::get('/v1/mock/tests/bar')
->desc('Get Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::post('/v1/mock/tests/bar')
->desc('Post Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.offline.model', '/mock/tests/bar')
->label('sdk.offline.key', '{required}')
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::patch('/v1/mock/tests/bar')
->desc('Patch Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a patch request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::put('/v1/mock/tests/bar')
->desc('Put Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::delete('/v1/mock/tests/bar')
->desc('Delete Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::get('/v1/mock/tests/general/headers')
->desc('Get headers')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'headers')
->label('sdk.description', 'Return headers from the request')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$res = [
'x-sdk-name' => $request->getHeader('x-sdk-name'),
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
'x-sdk-language' => $request->getHeader('x-sdk-language'),
'x-sdk-version' => $request->getHeader('x-sdk-version'),
];
$res = array_map(function ($key, $value) {
return $key . ': ' . $value;
}, array_keys($res), $res);
$res = implode("; ", $res);
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
});
App::get('/v1/mock/tests/general/download')
->desc('Download File')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'download')
->label('sdk.methodType', 'location')
->label('sdk.description', 'Mock a file download request.')
->label('sdk.response.type', '*/*')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response
->setContentType('text/plain')
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->send("GET:/v1/mock/tests/general/download:passed")
;
});
App::post('/v1/mock/tests/general/upload')
->desc('Upload File')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'upload')
->label('sdk.description', 'Mock a file upload request.')
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->param('file', [], new File(), 'Sample file param', skipValidation: true)
->inject('request')
->inject('response')
->action(function (string $x, int $y, array $z, mixed $file, Request $request, Response $response) {
$file = $request->getFiles('file');
$contentRange = $request->getHeader('content-range');
$chunkSize = 5 * 1024 * 1024; // 5MB
if (!empty($contentRange)) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$size = $request->getContentRangeSize();
$id = $request->getHeader('x-appwrite-id', '');
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
if (is_null($start) || is_null($end) || is_null($size) || $end >= $size) {
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
}
if ($start > $end || $end > $size) {
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
}
if ($start === 0 && !empty($id)) {
throw new Exception(Exception::GENERAL_MOCK, 'First chunked request cannot have id header');
}
if ($start !== 0 && $id !== 'newfileid') {
throw new Exception(Exception::GENERAL_MOCK, 'All chunked request must have id header (except first)');
}
if ($end !== $size - 1 && $end - $start + 1 !== $chunkSize) {
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB (except last chunk)');
}
if ($end !== $size - 1 && $file['size'] !== $chunkSize) {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong chunk size');
}
if ($file['size'] > $chunkSize) {
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB or less');
}
if ($end !== $size - 1) {
$response->json([
'$id' => ID::custom('newfileid'),
'chunksTotal' => (int) ceil($size / ($end + 1 - $start)),
'chunksUploaded' => ceil($start / $chunkSize) + 1
]);
}
} else {
$file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'][0] : $file['tmp_name'];
$file['name'] = (\is_array($file['name'])) ? $file['name'][0] : $file['name'];
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
if ($file['name'] !== 'file.png') {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file name');
}
if ($file['size'] !== 38756) {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file size');
}
if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file uploaded');
}
}
});
App::get('/v1/mock/tests/general/redirect')
->desc('Redirect')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirect')
->label('sdk.description', 'Mock a redirect request.')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response->redirect('/v1/mock/tests/general/redirect/done');
});
App::get('/v1/mock/tests/general/redirect/done')
->desc('Redirected')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirected')
->label('sdk.description', 'Mock a redirected request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->action(function () {
});
App::get('/v1/mock/tests/general/set-cookie')
->desc('Set Cookie')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'setCookie')
->label('sdk.description', 'Mock a set cookie request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('response')
->inject('request')
->action(function (Response $response, Request $request) {
$response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', $request->getHostname(), true, true);
});
App::get('/v1/mock/tests/general/get-cookie')
->desc('Get Cookie')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'getCookie')
->label('sdk.description', 'Mock a cookie response.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('request')
->action(function (Request $request) {
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value');
}
});
App::get('/v1/mock/tests/general/empty')
->desc('Empty Response')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'empty')
->label('sdk.description', 'Mock an empty response.')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response->noContent();
});
App::post('/v1/mock/tests/general/nullable')
->desc('Nullable Test')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'nullable')
->label('sdk.description', 'Mock a nullable parameter.')
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
->param('optional', '', new Text(100), 'Sample string param', true)
->action(function (string $required, string $nullable, ?string $optional) {
});
App::post('/v1/mock/tests/general/enum')
->desc('Enum Test')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'enum')
->label('sdk.description', 'Mock an enum parameter.')
->label('sdk.mock', true)
->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample enum param')
->action(function (string $mockType) {
});
App::get('/v1/mock/tests/general/400-error')
->desc('400 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error400')
->label('sdk.description', 'Mock a 400 failed request.')
->label('sdk.response.code', Response::STATUS_CODE_BAD_REQUEST)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ERROR)
->label('sdk.mock', true)
->action(function () {
throw new Exception(Exception::GENERAL_MOCK, 'Mock 400 error');
});
App::get('/v1/mock/tests/general/500-error')
->desc('500 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error500')
->label('sdk.description', 'Mock a 500 failed request.')
->label('sdk.response.code', Response::STATUS_CODE_INTERNAL_SERVER_ERROR)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ERROR)
->label('sdk.mock', true)
->action(function () {
throw new Exception(Exception::GENERAL_MOCK, 'Mock 500 error', 500);
});
App::get('/v1/mock/tests/general/502-error')
->desc('502 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error502')
->label('sdk.description', 'Mock a 502 bad gateway.')
->label('sdk.response.code', Response::STATUS_CODE_BAD_GATEWAY)
->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response
->setStatusCode(502)
->text('This is a text error')
;
});
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
App::get('/v1/mock/tests/general/oauth2')
->desc('OAuth Login')
@@ -646,6 +153,66 @@ App::patch('/v1/mock/functions-v2')
$response->noContent();
});
App::get('/v1/mock/github/callback')
->desc('Create installation document using GitHub installation id')
->groups(['mock', 'api', 'vcs'])
->label('scope', 'public')
->label('docs', false)
->param('providerInstallationId', '', new UID(), 'GitHub installation ID')
->param('projectId', '', new UID(), 'Project ID of the project where app is to be installed')
->inject('gitHub')
->inject('project')
->inject('response')
->inject('dbForConsole')
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) {
$isDevelopment = App::getEnv('_APP_ENV', 'development') === 'development';
if (!$isDevelopment) {
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
}
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
$error = 'Project with the ID from state could not be found.';
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
}
if (!empty($providerInstallationId)) {
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$projectInternalId = $project->getInternalId();
$teamId = $project->getAttribute('teamId', '');
$installation = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'providerInstallationId' => $providerInstallationId,
'projectId' => $projectId,
'projectInternalId' => $projectInternalId,
'provider' => 'github',
'organization' => $owner,
'personal' => false
]);
$installation = $dbForConsole->createDocument('installations', $installation);
}
$response->json([
'installationId' => $installation->getId(),
]);
});
App::shutdown()
->groups(['mock'])
->inject('utopia')
+37 -10
View File
@@ -154,8 +154,7 @@ App::init()
$response
->addHeader('X-RateLimit-Limit', $limit)
->addHeader('X-RateLimit-Remaining', $remaining)
->addHeader('X-RateLimit-Reset', $time)
;
->addHeader('X-RateLimit-Reset', $time);
}
$enabled = App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
@@ -170,6 +169,27 @@ App::init()
}
}
/*
* Session refresh
*/
if ($project->getAttribute('auths', [])['renewal'] ?? false) {
if ($user && !$user->isEmpty()) {
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$currentSessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret, $authDuration);
if ($currentSessionId) {
$currentSession = $dbForProject->getDocument('sessions', $currentSessionId);
if (!$currentSession->isEmpty()) {
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $authDuration));
$currentSession = $currentSession->setAttribute('expire', $expire);
$dbForProject->updateDocument('sessions', $currentSession->getId(), $currentSession);
$dbForProject->deleteCachedDocument('users', $user->getId());
}
}
}
}
/*
* Background Jobs
*/
@@ -240,7 +260,7 @@ App::init()
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -252,8 +272,7 @@ App::init()
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
->addHeader('X-Appwrite-Cache', 'hit')
->setContentType($data['contentType'])
->send(base64_decode($data['payload']))
;
->send(base64_decode($data['payload']));
} else {
$response->addHeader('X-Appwrite-Cache', 'miss');
}
@@ -495,7 +514,7 @@ App::shutdown()
'resource' => $resource,
'contentType' => $response->getContentType(),
'payload' => base64_encode($data['payload']),
]) ;
]);
$signature = md5($data);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
@@ -503,10 +522,10 @@ App::shutdown()
$now = DateTime::now();
if ($cacheLog->isEmpty()) {
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
'$id' => $key,
'resource' => $resource,
'accessedAt' => $now,
'signature' => $signature,
'$id' => $key,
'resource' => $resource,
'accessedAt' => $now,
'signature' => $signature,
])));
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$cacheLog->setAttribute('accessedAt', $now);
@@ -554,3 +573,11 @@ App::shutdown()
->submit();
}
});
App::init()
->groups(['usage'])
->action(function () {
if (App::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
throw new Exception(Exception::GENERAL_USAGE_DISABLED);
}
});
+171 -30
View File
@@ -25,7 +25,7 @@ use Appwrite\Event\Audit;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Event\Mail;
use Appwrite\Event\Phone;
use Appwrite\Event\Messaging;
use Appwrite\Event\Delete;
use Appwrite\GraphQL\Schema;
use Appwrite\Network\Validator\Email;
@@ -47,13 +47,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Structure;
use Utopia\Locale\Locale;
use Utopia\DSN\DSN;
use Utopia\Messaging\Adapters\SMS\Mock;
use Appwrite\GraphQL\Promises\Adapter\Swoole;
use Utopia\Messaging\Adapters\SMS\Msg91;
use Utopia\Messaging\Adapters\SMS\Telesign;
use Utopia\Messaging\Adapters\SMS\TextMagic;
use Utopia\Messaging\Adapters\SMS\Twilio;
use Utopia\Messaging\Adapters\SMS\Vonage;
use Utopia\Registry\Registry;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Backblaze;
@@ -83,6 +77,7 @@ use Utopia\Validator\Range;
use Utopia\Validator\IP;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
use Utopia\CLI\Console;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
@@ -103,14 +98,15 @@ const APP_LIMIT_COMPRESSION = 20000000; //20MB
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
const APP_LIMIT_SUBQUERY = 1000;
const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1000000;
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 514;
const APP_VERSION_STABLE = '1.4.6';
const APP_CACHE_BUSTER = 327;
const APP_VERSION_STABLE = '1.4.12';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -173,11 +169,14 @@ const DELETE_TYPE_SESSIONS = 'sessions';
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
const DELETE_TYPE_SCHEDULES = 'schedules';
const DELETE_TYPE_TOPIC = 'topic';
// Compression type
const COMPRESSION_TYPE_NONE = 'none';
const COMPRESSION_TYPE_GZIP = 'gzip';
const COMPRESSION_TYPE_ZSTD = 'zstd';
// Mail Types
const MAIL_TYPE_VERIFICATION_URL = 'verificationUrl';
const MAIL_TYPE_VERIFICATION_CODE = 'verificationCode';
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
const MAIL_TYPE_RECOVERY = 'recovery';
@@ -193,6 +192,10 @@ const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
// Function headers
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
// Message types
const MESSAGE_TYPE_EMAIL = 'email';
const MESSAGE_TYPE_SMS = 'sms';
const MESSAGE_TYPE_PUSH = 'push';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
@@ -237,7 +240,7 @@ App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
Config::load('events', __DIR__ . '/config/events.php');
Config::load('auth', __DIR__ . '/config/auth.php');
Config::load('errors', __DIR__ . '/config/errors.php');
Config::load('providers', __DIR__ . '/config/providers.php');
Config::load('oAuthProviders', __DIR__ . '/config/oAuthProviders.php');
Config::load('platforms', __DIR__ . '/config/platforms.php');
Config::load('collections', __DIR__ . '/config/collections.php');
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
@@ -526,6 +529,107 @@ Database::addFilter(
}
);
Database::addFilter(
'subQueryTargets',
function (mixed $value) {
return null;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn() => $database
->find('targets', [
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY)
]));
}
);
Database::addFilter(
'subQueryTopicTargets',
function (mixed $value) {
return null;
},
function (mixed $value, Document $document, Database $database) {
$targetIds = Authorization::skip(fn () => \array_map(
fn ($document) => $document->getAttribute('targetId'),
$database
->find('subscribers', [
Query::equal('topicInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
])
));
if (\count($targetIds) > 0) {
return $database->find('targets', [Query::equal('$id', $targetIds)]);
}
return [];
}
);
Database::addFilter(
'providerSearch',
function (mixed $value, Document $provider) {
$searchValues = [
$provider->getId(),
$provider->getAttribute('name', ''),
$provider->getAttribute('provider', ''),
$provider->getAttribute('type', '')
];
$search = \implode(' ', \array_filter($searchValues));
return $search;
},
function (mixed $value) {
return $value;
}
);
Database::addFilter(
'topicSearch',
function (mixed $value, Document $topic) {
$searchValues = [
$topic->getId(),
$topic->getAttribute('name', ''),
$topic->getAttribute('description', ''),
];
$search = \implode(' ', \array_filter($searchValues));
return $search;
},
function (mixed $value) {
return $value;
}
);
Database::addFilter(
'messageSearch',
function (mixed $value, Document $message) {
$searchValues = [
$message->getId(),
$message->getAttribute('description', ''),
$message->getAttribute('status', ''),
];
$data = \json_decode($message->getAttribute('data', []), true);
$providerType = $message->getAttribute('providerType', '');
if ($providerType === MESSAGE_TYPE_EMAIL) {
$searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
} elseif ($providerType === MESSAGE_TYPE_SMS) {
$searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
} else {
$searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
}
$search = \implode(' ', \array_filter($searchValues));
return $search;
},
function (mixed $value) {
return $value;
}
);
/**
* DB Formats
*/
@@ -823,12 +927,34 @@ $register->set('smtp', function () {
$register->set('geodb', function () {
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2023-01.mmdb');
});
$register->set('passwordsDictionary', function () {
$register->set('passwordsDictionary10k', function () {
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
$content = explode("\n", $content);
$content = array_flip($content);
return $content;
});
$register->set('passwordsDictionary100k', function () {
$content = \file_get_contents(__DIR__ . '/assets/security/100k-common-passwords.txt');
$content = explode("\n", $content);
$content = array_flip($content);
return $content;
});
$register->set('passwordsDictionary1M', function () {
$content = \file_get_contents(__DIR__ . '/assets/security/1m-common-passwords.txt');
$content = explode("\n", $content);
$content = array_flip($content);
return $content;
});
$register->set('passwordsDictionary10M', function () {
$content = \file_get_contents(__DIR__ . '/assets/security/1m-common-passwords.txt');
$content = explode("\n", $content);
$content = array_flip($content);
return $content;
});
$register->set('promiseAdapter', function () {
return new Swoole();
});
@@ -887,7 +1013,7 @@ App::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
}, ['pools']);
App::setResource('queueForMessaging', function (Connection $queue) {
return new Phone($queue);
return new Messaging($queue);
}, ['queue']);
App::setResource('queueForMails', function (Connection $queue) {
return new Mail($queue);
@@ -1049,6 +1175,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
}
}
$dbForProject->setMetadata('user', $user->getId());
$dbForConsole->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
@@ -1097,13 +1226,17 @@ App::setResource('console', function () {
'invites' => App::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
'limit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'notify' => true,
],
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
'authProviders' => [
'oAuthProviders' => [
'githubEnabled' => true,
'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', ''),
'supabaseEnabled' => true,
'supabaseSecret' => App::getEnv('_APP_CONSOLE_SUPABASE_SECRET', ''),
'supabaseAppid' => App::getEnv('_APP_CONSOLE_SUPABASE_APP_ID', ''),
],
]);
}, []);
@@ -1122,6 +1255,8 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
return $database;
@@ -1138,6 +1273,8 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
$database
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
return $database;
@@ -1158,6 +1295,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
return $database;
@@ -1174,6 +1313,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
return $database;
@@ -1311,26 +1452,26 @@ App::setResource('geodb', function ($register) {
return $register->get('geodb');
}, ['register']);
App::setResource('passwordsDictionary', function ($register) {
App::setResource('passwordsDictionary10k', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('passwordsDictionary');
return $register->get('passwordsDictionary10k');
}, ['register']);
App::setResource('sms', function () {
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
$user = $dsn->getUser();
$secret = $dsn->getPassword();
return match ($dsn->getHost()) {
'mock' => new Mock($user, $secret), // used for tests
'twilio' => new Twilio($user, $secret),
'text-magic' => new TextMagic($user, $secret),
'telesign' => new Telesign($user, $secret),
'msg91' => new Msg91($user, $secret),
'vonage' => new Vonage($user, $secret),
default => null
};
});
App::setResource('passwordsDictionary100k', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('passwordsDictionary100k');
}, ['register']);
App::setResource('passwordsDictionary1M', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('passwordsDictionary1M');
}, ['register']);
App::setResource('passwordsDictionary10M', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('passwordsDictionary10M');
}, ['register']);
App::setResource('servers', function () {
$platforms = Config::getParam('platforms');
+37 -25
View File
@@ -1,8 +1,10 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
@@ -20,7 +22,6 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Appwrite\Utopia\Request;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
@@ -28,6 +29,9 @@ use Utopia\Database\Database;
use Utopia\WebSocket\Server;
use Utopia\WebSocket\Adapter;
/**
* @var \Utopia\Registry\Registry $register
*/
require_once __DIR__ . '/init.php';
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
@@ -47,7 +51,10 @@ function getConsoleDB(): Database
$database = new Database($dbAdapter, getCache());
$database->setNamespace('_console');
$database
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', '_console');
return $database;
}
@@ -70,7 +77,11 @@ function getProjectDB(Document $project): Database
;
$database = new Database($dbAdapter, getCache());
$database->setNamespace('_' . $project->getInternalId());
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
}
@@ -122,12 +133,12 @@ $server = new Server($adapter);
$logError = function (Throwable $error, string $action) use ($register) {
$logger = $register->get('logger');
if ($logger) {
if ($logger && !$error instanceof Exception) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace("realtime");
$log->setServer(\gethostname());
$log->setServer(gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
@@ -182,7 +193,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
break;
} catch (\Throwable $th) {
} catch (Throwable) {
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
sleep(DATABASE_RECONNECT_SLEEP);
}
@@ -210,7 +221,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
->setAttribute('value', json_encode($payload));
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (\Throwable $th) {
} catch (Throwable $th) {
call_user_func($logError, $th, "updateWorkerDocument");
} finally {
$register->get('pools')->reclaim();
@@ -362,7 +373,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$stats->incr($event['project'], 'messages', $num);
}
});
} catch (\Throwable $th) {
} catch (Throwable $th) {
call_user_func($logError, $th, "pubSubConnection");
Console::error('Pub/sub error: ' . $th->getMessage());
@@ -389,19 +400,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
App::setResource('response', fn() => $response);
try {
/** @var \Utopia\Database\Document $project */
/** @var Document $project */
$project = $app->getResource('project');
/*
* Project Check
*/
if (empty($project->getId())) {
throw new Exception('Missing or unknown project ID', 1008);
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
}
$dbForProject = getProjectDB($project);
$console = $app->getResource('console'); /** @var \Utopia\Database\Document $console */
$user = $app->getResource('user'); /** @var \Utopia\Database\Document $user */
$console = $app->getResource('console'); /** @var Document $console */
$user = $app->getResource('user'); /** @var Document $user */
/*
* Abuse Check
@@ -416,7 +427,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$abuse = new Abuse($timeLimit);
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
throw new Exception('Too many requests', 1013);
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
}
/*
@@ -425,10 +436,10 @@ $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(array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if (!$originValidator->isValid($origin) && $project->getId() !== 'console') {
throw new Exception($originValidator->getDescription(), 1008);
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
$roles = Auth::getRoles($user);
@@ -439,7 +450,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
* Channels Check
*/
if (empty($channels)) {
throw new Exception('Missing channels', 1008);
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
}
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
@@ -460,7 +471,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
]);
$stats->incr($project->getId(), 'connections');
$stats->incr($project->getId(), 'connectionsTotal');
} catch (\Throwable $th) {
} catch (Throwable $th) {
call_user_func($logError, $th, "initServer");
$response = [
@@ -486,7 +497,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try {
$app = new App('UTC');
$response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId'];
$database = getConsoleDB();
@@ -494,6 +504,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
if ($projectId !== 'console') {
$project = Authorization::skip(fn() => $database->getDocument('projects', $projectId));
$database = getProjectDB($project);
} else {
$project = null;
}
/*
@@ -510,22 +522,22 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$abuse = new Abuse($timeLimit);
if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
throw new Exception('Too many messages', 1013);
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many messages.');
}
$message = json_decode($message, true);
if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) {
throw new Exception('Message format is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message format is not valid.');
}
switch ($message['type']) {
/**
/**
* This type is used to authenticate.
*/
case 'authentication':
if (!array_key_exists('session', $message['data'])) {
throw new Exception('Payload is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
}
$session = Auth::decodeSession($message['data']['session']);
@@ -540,7 +552,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration) // Validate user has valid login token
) {
// cookie not valid
throw new Exception('Session is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
}
$roles = Auth::getRoles($user);
@@ -560,9 +572,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
break;
default:
throw new Exception('Message type is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
}
} catch (\Throwable $th) {
} catch (Throwable $th) {
$response = [
'type' => 'error',
'data' => [
+1 -3
View File
@@ -12,7 +12,6 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Phone;
use Appwrite\Platform\Appwrite;
use Appwrite\Usage\Stats;
use Swoole\Runtime;
@@ -36,7 +35,6 @@ use Utopia\Queue\Connection;
Authorization::disable();
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
Server::setResource('register', fn () => $register);
Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
@@ -130,7 +128,7 @@ Server::setResource('queueForDatabase', function (Connection $queue) {
return new EventDatabase($queue);
}, ['queue']);
Server::setResource('queueForMessaging', function (Connection $queue) {
return new Phone($queue);
return new Messaging($queue);
}, ['queue']);
Server::setResource('queueForMails', function (Connection $queue) {
return new Mail($queue);
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php delete-orphaned-projects $@
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php patch-recreate-repositories-documents $@
+6 -6
View File
@@ -43,20 +43,20 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.13.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.32.*",
"utopia-php/abuse": "0.33.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.34.*",
"utopia-php/audit": "0.35.*",
"utopia-php/cache": "0.8.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.44.*",
"utopia-php/database": "0.45.*",
"utopia-php/domains": "0.3.*",
"utopia-php/dsn": "0.1.*",
"utopia-php/framework": "0.31.0",
"utopia-php/image": "0.5.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.1.*",
"utopia-php/messaging": "0.2.*",
"utopia-php/migration": "0.3.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.5.*",
@@ -64,9 +64,9 @@
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.5.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.17.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.5.*",
"utopia-php/vcs": "0.5.*",
"utopia-php/vcs": "0.6.*",
"utopia-php/websocket": "0.1.*",
"matomo/device-detector": "6.1.*",
"dragonmantank/cron-expression": "3.3.2",
Generated
+96 -162
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": "6ff937a260c3e0c09de9eb5e073d830d",
"content-hash": "69bc2e21a65b78344393706b39d789b4",
"packages": [
{
"name": "adhocore/jwt",
@@ -156,11 +156,11 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.13.1",
"version": "0.13.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "b584d19cdcd82737d0ee5c34d23de791f5ed3610"
"reference": "214a37c2c66e0f2bc9c30fdfde66955d9fd084a1"
},
"require": {
"php": ">=8.0",
@@ -195,7 +195,7 @@
"php",
"runtimes"
],
"time": "2023-10-16T15:39:53+00:00"
"time": "2023-11-22T15:36:00+00:00"
},
{
"name": "chillerlan/php-qrcode",
@@ -851,72 +851,6 @@
},
"time": "2021-10-08T21:21:46+00:00"
},
{
"name": "laravel/pint",
"version": "v1.2.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "e60e2112ee779ce60f253695b273d1646a17d6f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/e60e2112ee779ce60f253695b273d1646a17d6f1",
"reference": "e60e2112ee779ce60f253695b273d1646a17d6f1",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"php": "^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.11.0",
"illuminate/view": "^9.32.0",
"laravel-zero/framework": "^9.2.0",
"mockery/mockery": "^1.5.1",
"nunomaduro/larastan": "^2.2.0",
"nunomaduro/termwind": "^1.14.0",
"pestphp/pest": "^1.22.1"
},
"bin": [
"builds/pint"
],
"type": "project",
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Seeders\\": "database/seeders/",
"Database\\Factories\\": "database/factories/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
}
],
"description": "An opinionated code formatter for PHP.",
"homepage": "https://laravel.com",
"keywords": [
"format",
"formatter",
"lint",
"linter",
"php"
],
"support": {
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2022-11-29T16:25:20+00:00"
},
{
"name": "league/csv",
"version": "9.7.1",
@@ -1531,7 +1465,7 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.3.0",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
@@ -1578,7 +1512,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
},
"funding": [
{
@@ -1681,23 +1615,23 @@
},
{
"name": "utopia-php/abuse",
"version": "0.32.0",
"version": "0.33.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78"
"reference": "1ba8d5f2793885cbf779e3b5b9d886968af43d2c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/9717ffb2d7711f3fd621bb6df3edf5724c08ea78",
"reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/1ba8d5f2793885cbf779e3b5b9d886968af43d2c",
"reference": "1ba8d5f2793885cbf779e3b5b9d886968af43d2c",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-pdo": "*",
"php": ">=8.0",
"utopia-php/database": "0.44.*"
"utopia-php/database": "0.45.*"
},
"require-dev": {
"laravel/pint": "1.5.*",
@@ -1724,9 +1658,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
"source": "https://github.com/utopia-php/abuse/tree/0.32.0"
"source": "https://github.com/utopia-php/abuse/tree/0.33.0"
},
"time": "2023-10-18T07:28:55+00:00"
"time": "2023-11-01T08:51:33+00:00"
},
{
"name": "utopia-php/analytics",
@@ -1776,21 +1710,21 @@
},
{
"name": "utopia-php/audit",
"version": "0.34.0",
"version": "0.35.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f"
"reference": "ed9366ef05556da040de7a8b570f4160c7d8ea4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/cf34cc3f9f20da4e574a9be4517e1a11025a858f",
"reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/ed9366ef05556da040de7a8b570f4160c7d8ea4a",
"reference": "ed9366ef05556da040de7a8b570f4160c7d8ea4a",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/database": "0.44.*"
"utopia-php/database": "0.45.*"
},
"require-dev": {
"laravel/pint": "1.5.*",
@@ -1817,9 +1751,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/0.34.0"
"source": "https://github.com/utopia-php/audit/tree/0.35.0"
},
"time": "2023-10-18T07:43:25+00:00"
"time": "2023-11-01T08:51:29+00:00"
},
{
"name": "utopia-php/cache",
@@ -1972,16 +1906,16 @@
},
{
"name": "utopia-php/database",
"version": "0.44.2",
"version": "0.45.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06"
"reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
"reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
"url": "https://api.github.com/repos/utopia-php/database/zipball/dc789f2c1fd8b5ee07ff883e11c9ad7970824788",
"reference": "dc789f2c1fd8b5ee07ff883e11c9ad7970824788",
"shasum": ""
},
"require": {
@@ -1994,7 +1928,7 @@
},
"require-dev": {
"fakerphp/faker": "^1.14",
"laravel/pint": "1.4.*",
"laravel/pint": "1.13.*",
"pcov/clobber": "^2.0",
"phpstan/phpstan": "1.10.*",
"phpunit/phpunit": "^9.4",
@@ -2022,9 +1956,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.44.2"
"source": "https://github.com/utopia-php/database/tree/0.45.2"
},
"time": "2023-10-19T07:39:00+00:00"
"time": "2023-11-15T03:38:47+00:00"
},
{
"name": "utopia-php/domains",
@@ -2283,16 +2217,16 @@
},
{
"name": "utopia-php/logger",
"version": "0.3.1",
"version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/logger.git",
"reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc"
"reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/logger/zipball/de623f1ec1c672c795d113dd25c5bf212f7ef4fc",
"reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc",
"url": "https://api.github.com/repos/utopia-php/logger/zipball/ba763c10688fe2ed715ad2bed3f13d18dfec6253",
"reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253",
"shasum": ""
},
"require": {
@@ -2330,22 +2264,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/logger/issues",
"source": "https://github.com/utopia-php/logger/tree/0.3.1"
"source": "https://github.com/utopia-php/logger/tree/0.3.2"
},
"time": "2023-02-10T15:52:50+00:00"
"time": "2023-11-22T14:45:43+00:00"
},
{
"name": "utopia-php/messaging",
"version": "0.1.1",
"version": "0.2.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
"reference": "a75d66ddd59b834ab500a4878a2c084e6572604a"
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/a75d66ddd59b834ab500a4878a2c084e6572604a",
"reference": "a75d66ddd59b834ab500a4878a2c084e6572604a",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/2d0f474a106bb1da285f85e105c29b46085d3a43",
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43",
"shasum": ""
},
"require": {
@@ -2354,8 +2288,8 @@
},
"require-dev": {
"laravel/pint": "^1.2",
"phpmailer/phpmailer": "6.6.*",
"phpunit/phpunit": "9.5.*"
"phpmailer/phpmailer": "6.8.*",
"phpunit/phpunit": "9.6.*"
},
"type": "library",
"autoload": {
@@ -2378,22 +2312,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
"source": "https://github.com/utopia-php/messaging/tree/0.1.1"
"source": "https://github.com/utopia-php/messaging/tree/0.2.0"
},
"time": "2023-02-07T05:42:46+00:00"
"time": "2023-09-14T20:48:42+00:00"
},
{
"name": "utopia-php/migration",
"version": "0.3.5",
"version": "0.3.6",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83"
"reference": "f78273b38bade23db5866e5c7cb5f55427ba82af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83",
"reference": "b2fd3a8310296f4e44ff0e85b0eb0230ad9a2f83",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/f78273b38bade23db5866e5c7cb5f55427ba82af",
"reference": "f78273b38bade23db5866e5c7cb5f55427ba82af",
"shasum": ""
},
"require": {
@@ -2426,9 +2360,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.3.5"
"source": "https://github.com/utopia-php/migration/tree/0.3.6"
},
"time": "2023-09-25T16:51:47+00:00"
"time": "2023-11-02T15:13:03+00:00"
},
{
"name": "utopia-php/mongo",
@@ -2808,16 +2742,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.17.0",
"version": "0.18.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee"
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/efec5376c02d3d8330f1beb1469e6d6e313e21ee",
"reference": "efec5376c02d3d8330f1beb1469e6d6e313e21ee",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/983e6dee137012f9f57f126d3c79aab54e4e8824",
"reference": "983e6dee137012f9f57f126d3c79aab54e4e8824",
"shasum": ""
},
"require": {
@@ -2857,9 +2791,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.17.0"
"source": "https://github.com/utopia-php/storage/tree/0.18.1"
},
"time": "2023-08-21T11:28:36+00:00"
"time": "2023-10-24T14:44:19+00:00"
},
{
"name": "utopia-php/swoole",
@@ -2914,26 +2848,25 @@
},
{
"name": "utopia-php/system",
"version": "0.7.1",
"version": "0.7.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/system.git",
"reference": "01bf0d283aded0ee0a7a6e5ff540acf64270ab27"
"reference": "4593d4d334b0c15879c4744a826e0362924c5d66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/system/zipball/01bf0d283aded0ee0a7a6e5ff540acf64270ab27",
"reference": "01bf0d283aded0ee0a7a6e5ff540acf64270ab27",
"url": "https://api.github.com/repos/utopia-php/system/zipball/4593d4d334b0c15879c4744a826e0362924c5d66",
"reference": "4593d4d334b0c15879c4744a826e0362924c5d66",
"shasum": ""
},
"require": {
"laravel/pint": "1.2.*",
"php": ">=8.0.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.6",
"vimeo/psalm": "4.0.1"
"laravel/pint": "1.13.*",
"phpstan/phpstan": "1.10.*",
"phpunit/phpunit": "9.6.*"
},
"type": "library",
"autoload": {
@@ -2965,22 +2898,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/system/issues",
"source": "https://github.com/utopia-php/system/tree/0.7.1"
"source": "https://github.com/utopia-php/system/tree/0.7.2"
},
"time": "2023-08-30T09:14:37+00:00"
"time": "2023-10-20T01:39:17+00:00"
},
{
"name": "utopia-php/vcs",
"version": "0.5.0",
"version": "0.6.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/vcs.git",
"reference": "47144f272030b7ed1b05471f2cb3aabeb8cb831c"
"reference": "f135291b87cb45335fc6608722e7f89894bc33ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/47144f272030b7ed1b05471f2cb3aabeb8cb831c",
"reference": "47144f272030b7ed1b05471f2cb3aabeb8cb831c",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/f135291b87cb45335fc6608722e7f89894bc33ee",
"reference": "f135291b87cb45335fc6608722e7f89894bc33ee",
"shasum": ""
},
"require": {
@@ -3014,9 +2947,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/vcs/issues",
"source": "https://github.com/utopia-php/vcs/tree/0.5.0"
"source": "https://github.com/utopia-php/vcs/tree/0.6.2"
},
"time": "2023-09-13T19:05:52+00:00"
"time": "2023-11-08T15:36:03+00:00"
},
{
"name": "utopia-php/websocket",
@@ -3203,16 +3136,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.35.2",
"version": "0.35.3",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b"
"reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/2dfe0430a64ffd2a07078d83b20144b871acac3b",
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/4c431d5324a8f8cd2cab9a5515c170a5b427d44c",
"reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c",
"shasum": ""
},
"require": {
@@ -3248,9 +3181,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.35.2"
"source": "https://github.com/appwrite/sdk-generator/tree/0.35.3"
},
"time": "2023-09-14T14:59:50+00:00"
"time": "2023-11-12T05:56:27+00:00"
},
{
"name": "doctrine/deprecations",
@@ -3957,16 +3890,16 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.24.2",
"version": "1.24.4",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "bcad8d995980440892759db0c32acae7c8e79442"
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442",
"reference": "bcad8d995980440892759db0c32acae7c8e79442",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496",
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496",
"shasum": ""
},
"require": {
@@ -3998,9 +3931,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4"
},
"time": "2023-09-26T12:28:12+00:00"
"time": "2023-11-26T18:29:22+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -5743,16 +5676,16 @@
},
{
"name": "theseer/tokenizer",
"version": "1.2.1",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"shasum": ""
},
"require": {
@@ -5781,7 +5714,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.1"
"source": "https://github.com/theseer/tokenizer/tree/1.2.2"
},
"funding": [
{
@@ -5789,30 +5722,31 @@
"type": "github"
}
],
"time": "2021-07-28T10:34:58+00:00"
"time": "2023-11-20T00:12:19+00:00"
},
{
"name": "twig/twig",
"version": "v3.7.1",
"version": "v3.8.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554"
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php80": "^1.22"
},
"require-dev": {
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.3"
"symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0"
},
"type": "library",
"autoload": {
@@ -5848,7 +5782,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.7.1"
"source": "https://github.com/twigphp/Twig/tree/v3.8.0"
},
"funding": [
{
@@ -5860,7 +5794,7 @@
"type": "tidelift"
}
],
"time": "2023-08-28T11:09:02+00:00"
"time": "2023-11-21T18:54:41+00:00"
}
],
"aliases": [],
@@ -5889,5 +5823,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}
+13 -3
View File
@@ -188,7 +188,11 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_ASSISTANT_OPENAI_API_KEY
- _APP_MESSAGE_SMS_TEST_DSN
- _APP_MESSAGE_EMAIL_TEST_DSN
- _APP_MESSAGE_PUSH_TEST_DSN
- _APP_CONSOLE_SUPABASE_SECRET
- _APP_CONSOLE_SUPABASE_APP_ID
appwrite-realtime:
entrypoint: realtime
<<: *x-logging
@@ -568,14 +572,20 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
appwrite-worker-migrations:
entrypoint: worker-migrations
+3 -3
View File
@@ -37,7 +37,7 @@ Finally, you will need to create a `feat-XXX-YYY-oauth` branch based on the `mas
The first step in adding a new OAuth2 provider is to add it to the list of providers located at:
```
app/config/providers.php
app/config/oAuthProviders.php
```
Make sure to fill in all data needed and that your provider array key name:
@@ -45,7 +45,7 @@ Make sure to fill in all data needed and that your provider array key name:
- is in [`camelCase`](https://en.wikipedia.org/wiki/Camel_case) format for sentence, but lowercase for names. `github` must be all lowercased, but `paypalSandbox` should have uppercase S
- has no spaces or special characters
> Please make sure to keep the list of providers in `providers.php` in the alphabetical order A-Z.
> Please make sure to keep the list of providers in `oAuthProviders.php` in the alphabetical order A-Z.
### 2.2 Add Provider Logo
@@ -199,7 +199,7 @@ If you need any help with the contribution, feel free to head over to [our Disco
If your OAuth provider requires special configuration apart from `clientId` and `clientSecret` you can create a custom form. Currently this is being realized through putting all custom fields as JSON into the `clientSecret` field to keep the project API stable. You can implement your custom form following these steps:
1. Add your custom form in `app/views/console/users/oauth/[PROVIDER].phtml`. Below is a template you can use. Add the filename to `app/config/providers.php`.
1. Add your custom form in `app/views/console/users/oauth/[PROVIDER].phtml`. Below is a template you can use. Add the filename to `app/config/oAuthProviders.php`.
```php
<?php
+1
View File
@@ -32,6 +32,7 @@
<directory>./tests/e2e/Services/Projects</directory>
<directory>./tests/e2e/Services/Storage</directory>
<directory>./tests/e2e/Services/Webhooks</directory>
<directory>./tests/e2e/Services/Messaging</directory>
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</file>
+1 -1
View File
@@ -363,7 +363,7 @@ class Auth
$session->isSet('secret') &&
$session->isSet('provider') &&
$session->getAttribute('secret') === self::hash($secret) &&
DateTime::formatTz(DateTime::addSeconds(new \DateTime($session->getCreatedAt()), $expires)) >= DateTime::formatTz(DateTime::now())
DateTime::formatTz($session->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
) {
return $session->getId();
}
+182
View File
@@ -0,0 +1,182 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://supabase.com/docs/guides/platform/oauth-apps/build-a-supabase-integration
class Supabase extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://api.supabase.com/v1';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'supabase';
}
/**
* @return string
*/
public function getLoginURL(): string
{
$url = $this->endpoint . '/oauth/authorize?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback
]);
return $url;
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->callback,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . '/oauth/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['id'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['email'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @link https://discord.com/developers/docs/resources/user
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['username'] ?? '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request(
'GET',
$this->endpoint . '/organizations',
['Authorization: Bearer ' . \urlencode($accessToken)]
);
$this->user = \json_decode($user, true);
$this->user = [
'username' => $this->user[0]['name'],
'verified' => false,
'email' => $this->user[0]['name'] . '@supabase.internal',
'id' => $this->user[0]['id']
];
}
return $this->user;
}
}
+110
View File
@@ -0,0 +1,110 @@
<?php
namespace Appwrite\Auth\Validator;
/**
* Password.
*
* Validates user password string
*/
class PasswordAi extends Password
{
public function __construct()
{
}
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return 'Password must be at least 8 characters and must be approved by AI.';
}
/**
* Is valid.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (!parent::isValid($value)) {
return false;
}
$prompt = 'My password is "' . $value . '". Check my password and tell me if it could include day, year, month, date, name, surname, animal name, location, city, animal breed, or state in any shape or form. Answer "Yes" if it does, and "no" if it doesnt. Only answer with 1 word.';
$answer = '';
$ch = curl_init('http://appwrite-assistant:3003/');
$responseHeaders = [];
$query = json_encode(['prompt' => $prompt]);
$headers = ['accept: text/event-stream'];
$handleEvent = function ($ch, $data) use ($answer) {
$answer .= $data;
return \strlen($data);
};
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);
if (count($header) < 2) { // ignore invalid headers
return $len;
}
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
});
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
curl_exec($ch);
curl_close($ch);
if ($answer !== 'Yes') {
return false;
}
return true;
}
/**
* Is array
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
@@ -44,7 +44,7 @@ class PasswordHistory extends Password
public function isValid($value): bool
{
foreach ($this->history as $hash) {
if (Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) {
if (!empty($hash) && Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) {
return false;
}
}
+173
View File
@@ -0,0 +1,173 @@
<?php
namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\Queue\Connection;
use Utopia\Queue\Client;
class Messaging extends Event
{
protected ?string $messageId = null;
protected ?Document $message = null;
protected ?array $recipients = null;
protected ?string $scheduledAt = null;
protected ?string $providerType = null;
public function __construct(protected Connection $connection)
{
parent::__construct($connection);
$this
->setQueue(Event::MESSAGING_QUEUE_NAME)
->setClass(Event::MESSAGING_CLASS_NAME);
}
/**
* Sets recipient for the messaging event.
*
* @param string[] $recipients
* @return self
*/
public function setRecipients(array $recipients): self
{
$this->recipients = $recipients;
return $this;
}
/**
* Returns set recipient for messaging event.
*
* @return string[]
*/
public function getRecipient(): array
{
return $this->recipients;
}
/**
* Sets message document for the messaging event.
*
* @param Document $message
* @return self
*/
public function setMessage(Document $message): self
{
$this->message = $message;
return $this;
}
/**
* Returns message document for the messaging event.
*
* @return string
*/
public function getMessage(): Document
{
return $this->message;
}
/**
* Sets message ID for the messaging event.
*
* @param string $message
* @return self
*/
public function setMessageId(string $messageId): self
{
$this->messageId = $messageId;
return $this;
}
/**
* Returns set message ID for the messaging event.
*
* @return string
*/
public function getMessageId(): string
{
return $this->messageId;
}
/**
* Sets provider type for the messaging event.
*
* @param string $providerType
* @return self
*/
public function setProviderType(string $providerType): self
{
$this->providerType = $providerType;
return $this;
}
/**
* Returns set provider type for the messaging event.
*
* @return string
*/
public function getProviderType(): string
{
return $this->providerType;
}
/**
* Sets Scheduled delivery time for the messaging event.
*
* @param string $scheduledAt
* @return self
*/
public function setScheduledAt(string $scheduledAt): self
{
$this->scheduledAt = $scheduledAt;
return $this;
}
/**
* Returns set Delivery Time for the messaging event.
*
* @return string
*/
public function getScheduledAt(): string
{
return $this->scheduledAt;
}
/**
* Set project for this event.
*
* @param Document $project
* @return self
*/
public function setProject(Document $project): self
{
$this->project = $project;
return $this;
}
/**
* Executes the event and sends it to the messaging worker.
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string | bool
{
$client = new Client($this->queue, $this->connection);
return $client->enqueue([
'project' => $this->project,
'user' => $this->user,
'messageId' => $this->messageId,
'message' => $this->message,
'recipients' => $this->recipients,
'providerType' => $this->providerType,
]);
}
}
-87
View File
@@ -1,87 +0,0 @@
<?php
namespace Appwrite\Event;
use Utopia\Queue\Client;
use Utopia\Queue\Connection;
class Phone extends Event
{
protected string $recipient = '';
protected string $message = '';
public function __construct(protected Connection $connection)
{
parent::__construct($connection);
$this
->setQueue(Event::MESSAGING_QUEUE_NAME)
->setClass(Event::MESSAGING_CLASS_NAME);
}
/**
* Sets recipient for the messaging event.
*
* @param string $recipient
* @return self
*/
public function setRecipient(string $recipient): self
{
$this->recipient = $recipient;
return $this;
}
/**
* Returns set recipient for this messaging event.
*
* @return string
*/
public function getRecipient(): string
{
return $this->recipient;
}
/**
* Sets url for the messaging event.
*
* @param string $message
* @return self
*/
public function setMessage(string $message): self
{
$this->message = $message;
return $this;
}
/**
* Returns set url for the messaging event.
*
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* Executes the event and sends it to the messaging worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
$client = new Client($this->queue, $this->connection);
return $client->enqueue([
'project' => $this->project,
'user' => $this->user,
'payload' => $this->payload,
'recipient' => $this->recipient,
'message' => $this->message,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
]);
}
}
+36 -2
View File
@@ -55,6 +55,8 @@ class Exception extends \Exception
public const GENERAL_CODES_DISABLED = 'general_codes_disabled';
public const GENERAL_USAGE_DISABLED = 'general_usage_disabled';
public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented';
public const GENERAL_INVALID_EMAIL = 'general_invalid_email';
public const GENERAL_INVALID_PHONE = 'general_invalid_phone';
/** Users */
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
@@ -72,6 +74,8 @@ class Exception extends \Exception
public const USER_NOT_FOUND = 'user_not_found';
public const USER_PASSWORD_RECENTLY_USED = 'password_recently_used';
public const USER_PASSWORD_PERSONAL_DATA = 'password_personal_data';
public const USER_PASSWORD_DICTIONARY = 'password_dictionary';
public const USER_PASSWORD_AI = 'password_ai';
public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
public const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
public const USER_SESSION_NOT_FOUND = 'user_session_not_found';
@@ -84,6 +88,10 @@ class Exception extends \Exception
public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request';
public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized';
public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error';
public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_alread_verified';
public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified';
public const USER_TARGET_NOT_FOUND = 'user_target_not_found';
public const USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists';
/** Teams */
public const TEAM_NOT_FOUND = 'team_not_found';
@@ -224,9 +232,35 @@ class Exception extends \Exception
public const MIGRATION_NOT_FOUND = 'migration_not_found';
public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists';
public const MIGRATION_IN_PROGRESS = 'migration_in_progress';
public const MIGRATION_PROVIDER_ERROR = 'migration_provider_error';
protected $type = '';
protected $errors = [];
/** Realtime */
public const REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid';
public const REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages';
public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation';
/** Provider */
public const PROVIDER_NOT_FOUND = 'provider_not_found';
public const PROVIDER_ALREADY_EXISTS = 'provider_already_exists';
public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type';
public const PROVIDER_INTERNAL_UPDATE_DISABLED = 'provider_internal_update_disabled';
/** Topic */
public const TOPIC_NOT_FOUND = 'topic_not_found';
public const TOPIC_ALREADY_EXISTS = 'topic_already_exists';
/** Subscriber */
public const SUBSCRIBER_NOT_FOUND = 'subscriber_not_found';
public const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists';
/** Message */
public const MESSAGE_NOT_FOUND = 'message_not_found';
public const MESSAGE_MISSING_TARGET = 'message_missing_target';
public const MESSAGE_ALREADY_SENT = 'message_already_sent';
public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled';
protected string $type = '';
protected array $errors = [];
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
{
+6
View File
@@ -71,6 +71,12 @@ abstract class Migration
'1.4.4' => 'V19',
'1.4.5' => 'V19',
'1.4.6' => 'V19',
'1.4.7' => 'V19',
'1.4.8' => 'V19',
'1.4.9' => 'V19',
'1.4.10' => 'V19',
'1.4.11' => 'V19',
'1.4.12' => 'V19'
];
/**
+1 -1
View File
@@ -34,7 +34,7 @@ class V15 extends Migration
['email', 'anonymous'],
\array_map(
fn ($value) => "oauth-" . $value,
\array_keys(Config::getParam('providers', []))
\array_keys(Config::getParam('oAuthProviders', []))
)
);
+6 -6
View File
@@ -124,23 +124,23 @@ class V16 extends Migration
/**
* Enable OAuth providers with data
*/
$authProviders = $document->getAttribute('authProviders', []);
$oAuthProviders = $document->getAttribute('oAuthProviders', []);
foreach (Config::getParam('providers') as $provider => $value) {
foreach (Config::getParam('oAuthProviders') as $provider => $value) {
if (!$value['enabled']) {
continue;
}
if (($authProviders[$provider . 'Appid'] ?? false) && ($authProviders[$provider . 'Secret'] ?? false)) {
if (array_key_exists($provider . 'Enabled', $authProviders)) {
if (($oAuthProviders[$provider . 'Appid'] ?? false) && ($oAuthProviders[$provider . 'Secret'] ?? false)) {
if (array_key_exists($provider . 'Enabled', $oAuthProviders)) {
continue;
}
$authProviders[$provider . 'Enabled'] = true;
$oAuthProviders[$provider . 'Enabled'] = true;
}
}
$document->setAttribute('authProviders', $authProviders);
$document->setAttribute('oAuthProviders', $oAuthProviders);
break;
}
+5
View File
@@ -18,6 +18,8 @@ use Appwrite\Platform\Tasks\Version;
use Appwrite\Platform\Tasks\VolumeSync;
use Appwrite\Platform\Tasks\CalcTierStats;
use Appwrite\Platform\Tasks\Upgrade;
use Appwrite\Platform\Tasks\DeleteOrphanedProjects;
use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments;
class Tasks extends Service
{
@@ -40,6 +42,9 @@ class Tasks extends Service
->addAction(VolumeSync::getName(), new VolumeSync())
->addAction(Specs::getName(), new Specs())
->addAction(CalcTierStats::getName(), new CalcTierStats())
->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects())
->addAction(PatchRecreateRepositoriesDocuments::getName(), new PatchRecreateRepositoriesDocuments())
;
}
}
@@ -0,0 +1,140 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Query;
use Utopia\Platform\Action;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
use Utopia\Validator\Boolean;
class DeleteOrphanedProjects extends Action
{
public static function getName(): string
{
return 'delete-orphaned-projects';
}
public function __construct()
{
$this
->desc('Delete orphaned projects')
->param('commit', false, new Boolean(true), 'Commit project deletion', true)
->inject('pools')
->inject('cache')
->inject('dbForConsole')
->inject('register')
->callback(function (bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
$this->action($commit, $pools, $cache, $dbForConsole, $register);
});
}
public function action(bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
{
Console::title('Delete orphaned projects V1');
Console::success(APP_NAME . ' Delete orphaned projects started');
/** @var array $collections */
$collectionsConfig = Config::getParam('collections', [])['projects'] ?? [];
/* Initialise new Utopia app */
$app = new App('UTC');
$console = $app->getResource('console');
$projects = [$console];
/** Database connections */
$totalProjects = $dbForConsole->count('projects');
Console::success("Found a total of: {$totalProjects} projects");
$orphans = 0;
$cnt = 0;
$count = 0;
$limit = 30;
$sum = 30;
$offset = 0;
while (!empty($projects)) {
foreach ($projects as $project) {
/**
* Skip user projects with id 'console'
*/
if ($project->getId() === 'console') {
continue;
}
try {
$db = $project->getAttribute('database');
$adapter = $pools
->get($db)
->pop()
->getResource();
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDefaultDatabase('appwrite');
$dbForProject->setNamespace('_' . $project->getInternalId());
$collectionsCreated = 0;
$cnt++;
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) {
$collectionsCreated = $dbForProject->count(Database::METADATA);
}
$msg = '(' . $cnt . ') found (' . $collectionsCreated . ') collections on project (' . $project->getInternalId() . ') , database (' . $project['database'] . ')';
/**
* +2 = audit+abuse
*/
if ($collectionsCreated >= (count($collectionsConfig) + 2)) {
Console::log($msg . ' ignoring....');
continue;
}
Console::log($msg);
if ($collectionsCreated > 0) {
$collections = $dbForProject->find(Database::METADATA, []);
foreach ($collections as $collection) {
if ($commit) {
$dbForProject->deleteCollection($collection->getId());
$dbForConsole->deleteCachedCollection($collection->getId());
}
Console::info('--Deleting collection (' . $collection->getId() . ') project no (' . $project->getInternalId() . ')');
}
}
if ($commit) {
$dbForConsole->deleteDocument('projects', $project->getId());
$dbForConsole->deleteCachedDocument('projects', $project->getId());
}
Console::info('--Deleting project no (' . $project->getInternalId() . ')');
$orphans++;
} catch (\Throwable $th) {
Console::error('Error: ' . $th->getMessage());
} finally {
$pools
->get($db)
->reclaim();
}
}
$sum = \count($projects);
$projects = $dbForConsole->find('projects', [
Query::limit($limit),
Query::offset($offset),
]);
$offset = $offset + $limit;
$count = $count + $sum;
}
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans . ' orphans');
}
}
+1 -29
View File
@@ -6,9 +6,6 @@ use Appwrite\Auth\Auth;
use Appwrite\Docker\Compose;
use Appwrite\Docker\Env;
use Appwrite\Utopia\View;
use Utopia\Analytics\Adapter;
use Utopia\Analytics\Adapter\GoogleAnalytics;
use Utopia\Analytics\Event;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Validator\Text;
@@ -43,12 +40,6 @@ class Install extends Action
/** @var array<string, array<string, string>> $vars array whre key is variable name and value is variable */
$vars = [];
/**
* We are using a random value every execution for identification.
* This allows us to collect information without invading the privacy of our users.
*/
$analytics = new GoogleAnalytics('UA-26264668-9', uniqid('server.', true));
foreach ($config as $category) {
foreach ($category['variables'] ?? [] as $var) {
$vars[$var['name']] = $var;
@@ -82,7 +73,7 @@ class Install extends Action
file_put_contents($this->path . '/docker-compose.yml.' . $time . '.backup', $data);
$compose = new Compose($data);
$appwrite = $compose->getService('appwrite');
$oldVersion = ($appwrite) ? $appwrite->getImageVersion() : null;
$oldVersion = $appwrite?->getImageVersion();
try {
$ports = $compose->getService('traefik')->getPorts();
} catch (\Throwable $th) {
@@ -209,14 +200,12 @@ class Install extends Action
if (!file_put_contents($this->path . '/docker-compose.yml', $templateForCompose->render(false))) {
$message = 'Failed to save Docker Compose file';
$this->sendEvent($analytics, $message);
Console::error($message);
Console::exit(1);
}
if (!file_put_contents($this->path . '/.env', $templateForEnv->render(false))) {
$message = 'Failed to save environment variables file';
$this->sendEvent($analytics, $message);
Console::error($message);
Console::exit(1);
}
@@ -237,29 +226,12 @@ class Install extends Action
if ($exit !== 0) {
$message = 'Failed to install Appwrite dockers';
$this->sendEvent($analytics, $message);
Console::error($message);
Console::error($stderr);
Console::exit($exit);
} else {
$message = 'Appwrite installed successfully';
$this->sendEvent($analytics, $message);
Console::success($message);
}
}
private function sendEvent(Adapter $analytics, string $message): void
{
$event = new Event();
$event->setName(APP_VERSION_STABLE);
$event->setValue($message);
$event->setUrl('http://localhost/');
$event->setProps([
'category' => 'install/server',
'action' => 'install',
]);
$event->setType('install/server');
$analytics->createEvent($event);
}
}
@@ -0,0 +1,169 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\Platform\Action;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Validator\Text;
class PatchRecreateRepositoriesDocuments extends Action
{
public static function getName(): string
{
return 'patch-recreate-repositories-documents';
}
public function __construct()
{
$this
->desc('Recreate missing repositories in consoleDB from projectDBs. They can be missing if you used Appwrite 1.4.10 or 1.4.11, and deleted a function.')
->param('after', '', new Text(36), 'After cursor', true)
->param('projectId', '', new Text(36), 'Select project to validate', true)
->inject('dbForConsole')
->inject('getProjectDB')
->callback(fn ($after, $projectId, $dbForConsole, $getProjectDB) => $this->action($after, $projectId, $dbForConsole, $getProjectDB));
}
public function action($after, $projectId, Database $dbForConsole, callable $getProjectDB): void
{
Console::info("Starting the patch");
$startTime = microtime(true);
if (!empty($projectId)) {
try {
$project = $dbForConsole->getDocument('projects', $projectId);
$dbForProject = call_user_func($getProjectDB, $project);
$this->recreateRepositories($dbForConsole, $dbForProject, $project);
} catch (\Throwable $th) {
Console::error("Unexpected error occured with Project ID {$projectId}");
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
} else {
$queries = [];
if (!empty($after)) {
Console::info("Iterating remaining projects after project with ID {$after}");
$project = $dbForConsole->getDocument('projects', $after);
$queries = [Query::cursorAfter($project)];
} else {
Console::info("Iterating all projects");
}
$this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB, $dbForConsole) {
$projectId = $project->getId();
try {
$dbForProject = call_user_func($getProjectDB, $project);
$this->recreateRepositories($dbForConsole, $dbForProject, $project);
} catch (\Throwable $th) {
Console::error("Unexpected error occured with Project ID {$projectId}");
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
});
}
$endTime = microtime(true);
$timeTaken = $endTime - $startTime;
$hours = (int)($timeTaken / 3600);
$timeTaken -= $hours * 3600;
$minutes = (int)($timeTaken / 60);
$timeTaken -= $minutes * 60;
$seconds = (int)$timeTaken;
$milliseconds = ($timeTaken - $seconds) * 1000;
Console::info("Recreate patch completed in $hours h, $minutes m, $seconds s, $milliseconds mis ( total $timeTaken milliseconds)");
}
protected function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void
{
$limit = 1000;
$results = [];
$sum = $limit;
$latestDocument = null;
while ($sum === $limit) {
$newQueries = $queries;
if ($latestDocument != null) {
array_unshift($newQueries, Query::cursorAfter($latestDocument));
}
$newQueries[] = Query::limit($limit);
$results = $database->find($collection, $newQueries);
if (empty($results)) {
return;
}
$sum = count($results);
foreach ($results as $document) {
if (is_callable($callback)) {
$callback($document);
}
}
$latestDocument = $results[array_key_last($results)];
}
}
public function recreateRepositories(Database $dbForConsole, Database $dbForProject, Document $project): void
{
$projectId = $project->getId();
Console::log("Running patch for project {$projectId}");
$this->foreachDocument($dbForProject, 'functions', [], function (Document $function) use ($dbForProject, $dbForConsole, $project) {
$isConnected = !empty($function->getAttribute('providerRepositoryId', ''));
if ($isConnected) {
$repository = $dbForConsole->getDocument('repositories', $function->getAttribute('repositoryId', ''));
if ($repository->isEmpty()) {
$projectId = $project->getId();
$functionId = $function->getId();
Console::success("Recreating repositories document for project ID {$projectId}, function ID {$functionId}");
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'installationId' => $function->getAttribute('installationId', ''),
'installationInternalId' => $function->getAttribute('installationInternalId', ''),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $function->getAttribute('providerRepositoryId', ''),
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'function',
'providerPullRequestIds' => []
]));
$function = $dbForProject->updateDocument('functions', $function->getId(), $function
->setAttribute('repositoryId', $repository->getId())
->setAttribute('repositoryInternalId', $repository->getInternalId()));
$this->foreachDocument($dbForProject, 'deployments', [
Query::equal('resourceInternalId', [$function->getInternalId()]),
Query::equal('resourceType', ['functions'])
], function (Document $deployment) use ($dbForProject, $repository) {
$dbForProject->updateDocument('deployments', $deployment->getId(), $deployment
->setAttribute('repositoryId', $repository->getId())
->setAttribute('repositoryInternalId', $repository->getInternalId()));
});
}
}
});
}
}
+18 -16
View File
@@ -426,29 +426,31 @@ class Certificates extends Action
$locale->setDefault('en');
}
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
$body
->setParam('{{domain}}', $domain)
->setParam('{{error}}', $errorMessage)
->setParam('{{attempt}}', $attempt)
->setParam('{{subject}}', $subject)
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
$message = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{body}}', $locale->getText("emails.certificate.body"))
->setParam('{{redirect}}', 'https://' . $domain)
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
->setParam('{{footer}}', $locale->getText("emails.certificate.footer"))
->setParam('{{thanks}}', $locale->getText("emails.certificate.thanks"))
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"))
->setParam('{{project}}', 'Console')
->setParam('{{direction}}', $locale->getText('settings.direction'))
->setParam('{{bg-body}}', '#f7f7f7')
->setParam('{{bg-content}}', '#ffffff')
->setParam('{{text-content}}', '#000000');
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"));
$body = $message->render();
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
'domain' => $domain,
'error' => '<br><pre>' . $errorMessage . '</pre>',
'attempt' => $attempt,
'project' => 'Console',
'redirect' => 'https://' . $domain,
];
$queueForMails
->setRecipient(App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
->setBody($body->render())
->setSubject($subject)
->setBody($body)
->setVariables($emailVariables)
->setName('Appwrite Administrator')
->trigger();
}
+71 -4
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Workers;
use Appwrite\Auth\Auth;
use Executor\Executor;
use Throwable;
use Utopia\Abuse\Abuse;
@@ -45,7 +46,7 @@ class Deletes extends Action
->inject('getFunctionsDevice')
->inject('getBuildsDevice')
->inject('getCacheDevice')
->callback(fn($message, $dbForConsole, callable $getProjectDB, callable $getFilesDevice, callable $getFunctionsDevice, callable $getBuildsDevice, callable $getCacheDevice) => $this->action($message, $dbForConsole, $getProjectDB, $getFilesDevice, $getFunctionsDevice, $getBuildsDevice, $getCacheDevice));
->callback(fn ($message, $dbForConsole, callable $getProjectDB, callable $getFilesDevice, callable $getFunctionsDevice, callable $getBuildsDevice, callable $getCacheDevice) => $this->action($message, $dbForConsole, $getProjectDB, $getFilesDevice, $getFunctionsDevice, $getBuildsDevice, $getCacheDevice));
}
/**
@@ -149,6 +150,9 @@ class Deletes extends Action
case DELETE_TYPE_SCHEDULES:
$this->deleteSchedules($dbForConsole, $getProjectDB, $datetime);
break;
case DELETE_TYPE_TOPIC:
$this->deleteTopic($project, $getProjectDB, $document);
break;
default:
Console::error('No delete operation for type: ' . $type);
break;
@@ -178,7 +182,8 @@ class Deletes extends Action
$project = $dbForConsole->getDocument('projects', $document->getAttribute('projectId'));
if ($project->isEmpty()) {
Console::warning('Unable to delete schedule for function ' . $document->getAttribute('resourceId'));
$dbForConsole->deleteDocument('schedules', $document->getId());
Console::success('Deleted schedule for deleted project ' . $document->getAttribute('projectId'));
return;
}
@@ -192,6 +197,25 @@ class Deletes extends Action
);
}
/**
* @param Document $project
* @param callable $getProjectDB
* @param Document $topic
* @throws Exception
*/
protected function deleteTopic(Document $project, callable $getProjectDB, Document $topic)
{
if ($topic->isEmpty()) {
Console::error('Failed to delete subscribers. Topic not found');
return;
}
$dbForProject = $getProjectDB($project);
$this->deleteByGroup('subscribers', [
Query::equal('topicInternalId', [$topic->getInternalId()])
], $dbForProject);
}
/**
* @param Document $project
* @param callable $getProjectDB
@@ -450,6 +474,21 @@ class Deletes extends Action
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
// Delete VCS Installations
$this->deleteByGroup('installations', [
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
// Delete VCS Repositories
$this->deleteByGroup('repositories', [
Query::equal('projectInternalId', [$projectInternalId]),
], $dbForConsole);
// Delete VCS commments
$this->deleteByGroup('vcsComments', [
Query::equal('projectInternalId', [$projectInternalId]),
], $dbForConsole);
// Delete metadata tables
try {
$dbForProject->deleteCollection('_metadata');
@@ -688,7 +727,7 @@ class Deletes extends Action
$this->deleteDeploymentFiles($functionsStorage, $document);
});
/**
/**
* Delete builds
*/
Console::info("Deleting builds for function " . $functionId);
@@ -709,6 +748,23 @@ class Deletes extends Action
Query::equal('functionInternalId', [$functionInternalId])
], $dbForProject);
/**
* Delete VCS Repositories and VCS Comments
*/
Console::info("Deleting VCS repositories and comments linked to function " . $functionId);
$this->deleteByGroup('repositories', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('resourceInternalId', [$functionInternalId]),
Query::equal('resourceType', ['function']),
], $dbForConsole, function (Document $document) use ($dbForConsole) {
$providerRepositoryId = $document->getAttribute('providerRepositoryId', '');
$projectInternalId = $document->getAttribute('projectInternalId', '');
$this->deleteByGroup('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('projectInternalId', [$projectInternalId]),
], $dbForConsole);
});
/**
* Request executor to delete all deployment containers
*/
@@ -920,17 +976,28 @@ class Deletes extends Action
$count = 0;
$chunk = 0;
$limit = 50;
$results = [];
$sum = $limit;
$cursor = null;
$executionStart = \microtime(true);
while ($sum === $limit) {
$chunk++;
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
$mergedQueries = \array_merge([Query::limit($limit)], $queries);
if ($cursor instanceof Document) {
$mergedQueries[] = Query::cursorAfter($cursor);
}
$results = $database->find($collection, $mergedQueries);
$sum = count($results);
if ($sum > 0) {
$cursor = $results[$sum - 1];
}
foreach ($results as $document) {
if (is_callable($callback)) {
$callback($document);
+1 -1
View File
@@ -77,7 +77,7 @@ class Functions extends Action
$type = $payload['type'] ?? '';
$events = $payload['events'] ?? [];
$data = $payload['data'] ?? '';
$data = $payload['body'] ?? '';
$eventData = $payload['payload'] ?? '';
$project = new Document($payload['project'] ?? []);
$function = new Document($payload['function'] ?? []);
+11 -3
View File
@@ -58,13 +58,21 @@ class Mails extends Action
$subject = $payload['subject'];
$variables = $payload['variables'];
$name = $payload['name'];
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
$body = $payload['body'];
$bodyTemplate = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base.tpl');
$bodyTemplate->setParam('{{body}}', $body);
foreach ($variables as $key => $value) {
$body->setParam('{{' . $key . '}}', $value);
$bodyTemplate->setParam('{{' . $key . '}}', $value);
}
$body = $bodyTemplate->render();
$body = $body->render();
$subjectTemplate = Template::fromString($subject);
foreach ($variables as $key => $value) {
$subjectTemplate->setParam('{{' . $key . '}}', $value);
}
// render() will return the subject in <p> tags, so use strip_tags() to remove them
$subject = \strip_tags($subjectTemplate->render());
/** @var PHPMailer $mail */
$mail = empty($smtp)
+329 -55
View File
@@ -2,30 +2,41 @@
namespace Appwrite\Platform\Workers;
use Exception;
use Appwrite\Extend\Exception;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Helpers\ID;
use Utopia\DSN\DSN;
use Utopia\Messaging\Messages\Sms;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Messaging\Adapters\SMS as SMSAdapter;
use Utopia\Messaging\Adapters\SMS\Mock;
use Utopia\Messaging\Adapters\SMS\Msg91;
use Utopia\Messaging\Adapters\SMS\Telesign;
use Utopia\Messaging\Adapters\SMS\TextMagic;
use Utopia\Messaging\Adapters\SMS\Textmagic;
use Utopia\Messaging\Adapters\SMS\Twilio;
use Utopia\Messaging\Adapters\SMS\Vonage;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Messaging\Adapters\Push as PushAdapter;
use Utopia\Messaging\Adapters\Push\APNS;
use Utopia\Messaging\Adapters\Push\FCM;
use Utopia\Messaging\Adapters\Email as EmailAdapter;
use Utopia\Messaging\Adapters\Email\Mailgun;
use Utopia\Messaging\Adapters\Email\SendGrid;
use Utopia\Messaging\Messages\Email;
use Utopia\Messaging\Messages\Push;
use Utopia\Messaging\Messages\SMS;
use function Swoole\Coroutine\batch;
class Messaging extends Action
{
private ?DSN $dsn = null;
private string $user = '';
private string $secret = '';
private string $provider = '';
public static function getName(): string
{
return 'messaging';
return "messaging";
}
/**
@@ -33,25 +44,20 @@ class Messaging extends Action
*/
public function __construct()
{
$this->provider = App::getEnv('_APP_SMS_PROVIDER', '');
if (!empty($this->provider)) {
$this->dsn = new DSN($this->provider);
$this->user = $this->dsn->getUser();
$this->secret = $this->dsn->getPassword();
}
$this
->desc('Messaging worker')
->inject('message')
->callback(fn($message) => $this->action($message));
->inject('dbForProject')
->callback(fn(Message $message, Database $dbForProject) => $this->action($message, $dbForProject));
}
/**
* @param Message $message
* @param Database $dbForProject
* @return void
* @throws Exception
*/
public function action(Message $message): void
public function action(Message $message, Database $dbForProject): void
{
$payload = $message->getPayload() ?? [];
@@ -60,48 +66,316 @@ class Messaging extends Action
return;
}
if (empty($payload['recipient'])) {
Console::error('Recipient arg not found');
if (!\is_null($payload['message']) && !\is_null($payload['recipients'])) {
if ($payload['providerType'] === MESSAGE_TYPE_SMS) {
$this->processInternalSMSMessage(new Document($payload['message']), $payload['recipients']);
}
} else {
$message = $dbForProject->getDocument('messages', $payload['messageId']);
$this->processMessage($dbForProject, $message);
}
}
private function processMessage(Database $dbForProject, Document $message): void
{
$topicsId = $message->getAttribute('topics', []);
$targetsId = $message->getAttribute('targets', []);
$usersId = $message->getAttribute('users', []);
/**
* @var Document[] $recipients
*/
$recipients = [];
if (\count($topicsId) > 0) {
$topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]);
foreach ($topics as $topic) {
$recipients = \array_merge($recipients, $topic->getAttribute('targets'));
}
}
if (\count($usersId) > 0) {
$users = $dbForProject->find('users', [Query::equal('$id', $usersId)]);
foreach ($users as $user) {
$recipients = \array_merge($recipients, $user->getAttribute('targets'));
}
}
if (\count($targetsId) > 0) {
$targets = $dbForProject->find('targets', [Query::equal('$id', $targetsId)]);
$recipients = \array_merge($recipients, $targets);
}
$primaryProvider = $dbForProject->findOne('providers', [
Query::equal('enabled', [true]),
Query::equal('type', [$recipients[0]->getAttribute('providerType')]),
]);
/**
* @var array<string, array<string>> $identifiersByProviderId
*/
$identifiersByProviderId = [];
/**
* @var Document[] $providers
*/
$providers = [];
foreach ($recipients as $recipient) {
$providerId = $recipient->getAttribute('providerId');
if (!$providerId && $primaryProvider instanceof Document && !$primaryProvider->isEmpty()) {
$providerId = $primaryProvider->getId();
}
if ($providerId) {
if (!isset($identifiersByProviderId[$providerId])) {
$identifiersByProviderId[$providerId] = [];
}
$identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier');
}
}
/**
* @var array[] $results
*/
$results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
return function () use ($providerId, $identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
$provider = new Document();
if ($primaryProvider->getId() === $providerId) {
$provider = $primaryProvider;
} else {
$provider = $dbForProject->getDocument('providers', $providerId, [Query::equal('enabled', [true])]);
if ($provider->isEmpty()) {
$provider = $primaryProvider;
}
}
$providers[] = $provider;
$identifiers = $identifiersByProviderId[$providerId];
$adapter = match ($provider->getAttribute('type')) {
MESSAGE_TYPE_SMS => $this->sms($provider),
MESSAGE_TYPE_PUSH => $this->push($provider),
MESSAGE_TYPE_EMAIL => $this->email($provider),
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
};
$maxBatchSize = $adapter->getMaxMessagesPerRequest();
$batches = \array_chunk($identifiers, $maxBatchSize);
$batchIndex = 0;
$results = batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
$deliveredTotal = 0;
$deliveryErrors = [];
$messageData = clone $message;
$messageData->setAttribute('to', $batch);
$data = match ($provider->getAttribute('type')) {
MESSAGE_TYPE_SMS => $this->buildSMSMessage($messageData, $provider),
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($messageData, $provider),
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
};
try {
$adapter->send($data);
$deliveredTotal += \count($batch);
} catch (\Exception $e) {
$deliveryErrors[] = 'Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage();
} finally {
$batchIndex++;
return [
'deliveredTotal' => $deliveredTotal,
'deliveryErrors' => $deliveryErrors,
];
}
};
}, $batches));
return $results;
};
}, \array_keys($identifiersByProviderId)));
$results = array_merge(...$results);
$deliveredTotal = 0;
$deliveryErrors = [];
foreach ($results as $result) {
$deliveredTotal += $result['deliveredTotal'];
$deliveryErrors = \array_merge($deliveryErrors, $result['deliveryErrors']);
}
$message->setAttribute('deliveryErrors', $deliveryErrors);
if (\count($message->getAttribute('deliveryErrors')) > 0) {
$message->setAttribute('status', 'failed');
} else {
$message->setAttribute('status', 'sent');
}
$message->removeAttribute('to');
foreach ($providers as $provider) {
$message->setAttribute('search', "{$message->getAttribute('search')} {$provider->getAttribute('name')} {$provider->getAttribute('provider')} {$provider->getAttribute('type')}");
}
$message->setAttribute('deliveredTotal', $deliveredTotal);
$message->setAttribute('deliveredAt', DateTime::now());
$dbForProject->updateDocument('messages', $message->getId(), $message);
}
private function processInternalSMSMessage(Document $message, array $recipients): void
{
if (empty(App::getEnv('_APP_SMS_PROVIDER')) || empty(App::getEnv('_APP_SMS_FROM'))) {
Console::info('Skipped SMS processing. No Phone configuration has been set.');
return;
}
if (empty($payload['message'])) {
Console::error('Message arg not found');
return;
}
$sms = match ($this->dsn->getHost()) {
'mock' => new Mock($this->user, $this->secret), // used for tests
'twilio' => new Twilio($this->user, $this->secret),
'text-magic' => new TextMagic($this->user, $this->secret),
'telesign' => new Telesign($this->user, $this->secret),
'msg91' => new Msg91($this->user, $this->secret),
'vonage' => new Vonage($this->user, $this->secret),
default => null
};
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
Console::error('Skipped sms processing. No Phone provider has been set.');
return;
}
$smsDSN = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
$host = $smsDSN->getHost();
$password = $smsDSN->getPassword();
$user = $smsDSN->getUser();
$from = App::getEnv('_APP_SMS_FROM');
if (empty($from)) {
Console::error('Skipped sms processing. No phone number has been set.');
return;
}
$provider = new Document([
'$id' => ID::unique(),
'provider' => $host,
'type' => MESSAGE_TYPE_SMS,
'name' => 'Internal SMS',
'enabled' => true,
'credentials' => match ($host) {
'twilio' => [
'accountSid' => $user,
'authToken' => $password
],
'textmagic' => [
'username' => $user,
'apiKey' => $password
],
'telesign' => [
'username' => $user,
'password' => $password
],
'msg91' => [
'senderId' => $user,
'authKey' => $password
],
'vonage' => [
'apiKey' => $user,
'apiSecret' => $password
],
default => null
},
'options' => [
'from' => $from
]
]);
$message = new SMS(
to: [$payload['recipient']],
content: $payload['message'],
from: $from,
);
$adapter = $this->sms($provider);
try {
$sms->send($message);
} catch (\Exception $error) {
throw new Exception('Error sending message: ' . $error->getMessage(), 500);
}
$maxBatchSize = $adapter->getMaxMessagesPerRequest();
$batches = \array_chunk($recipients, $maxBatchSize);
$batchIndex = 0;
batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
$message->setAttribute('to', $batch);
$data = $this->buildSMSMessage($message, $provider);
try {
$adapter->send($data);
} catch (\Exception $e) {
Console::error('Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage());
}
};
}, $batches));
}
public function shutdown(): void
{
}
private function sms(Document $provider): ?SMSAdapter
{
$credentials = $provider->getAttribute('credentials');
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']),
'textmagic' => new Textmagic($credentials['username'], $credentials['apiKey']),
'telesign' => new Telesign($credentials['username'], $credentials['password']),
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']),
'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),
default => null
};
}
private function push(Document $provider): ?PushAdapter
{
$credentials = $provider->getAttribute('credentials');
return match ($provider->getAttribute('provider')) {
'apns' => new APNS(
$credentials['authKey'],
$credentials['authKeyId'],
$credentials['teamId'],
$credentials['bundleId'],
$credentials['endpoint']
),
'fcm' => new FCM($credentials['serverKey']),
default => null
};
}
private function email(Document $provider): ?EmailAdapter
{
$credentials = $provider->getAttribute('credentials');
return match ($provider->getAttribute('provider')) {
'mailgun' => new Mailgun($credentials['apiKey'], $credentials['domain'], $credentials['isEuRegion']),
'sendgrid' => new SendGrid($credentials['apiKey']),
default => null
};
}
private function buildEmailMessage(Document $message, Document $provider): Email
{
$from = $provider['options']['from'];
$to = $message['to'];
$subject = $message['data']['subject'];
$content = $message['data']['content'];
$html = $message['data']['html'];
return new Email($to, $subject, $content, $from, null, $html);
}
private function buildSMSMessage(Document $message, Document $provider): SMS
{
$to = $message['to'];
$content = $message['data']['content'];
$from = $provider['options']['from'];
return new SMS($to, $content, $from);
}
private function buildPushMessage(Document $message): Push
{
$to = $message['to'];
$title = $message['data']['title'];
$body = $message['data']['body'];
$data = $message['data']['data'];
$action = $message['data']['action'];
$sound = $message['data']['sound'];
$icon = $message['data']['icon'];
$color = $message['data']['color'];
$tag = $message['data']['tag'];
$badge = $message['data']['badge'];
return new Push($to, $title, $body, $data, $action, $sound, $icon, $color, $tag, $badge);
}
}
+14 -11
View File
@@ -25,7 +25,7 @@ class Webhooks extends Action
$this
->desc('Webhooks worker')
->inject('message')
->callback(fn($message) => $this->action($message));
->callback(fn ($message) => $this->action($message));
}
/**
@@ -48,7 +48,7 @@ class Webhooks extends Action
foreach ($project->getAttribute('webhooks', []) as $webhook) {
if (array_intersect($webhook->getAttribute('events', []), $events)) {
$this->execute($events, $webhookPayload, $webhook, $user, $project);
$this->execute($events, $webhookPayload, $webhook, $user, $project);
}
}
@@ -78,7 +78,9 @@ class Webhooks extends Action
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
\curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
\curl_setopt($ch, CURLOPT_HEADER, 0);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
\curl_setopt($ch, CURLOPT_TIMEOUT, 15);
\curl_setopt($ch, CURLOPT_MAXFILESIZE, 5242880);
\curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(
APP_USERAGENT,
App::getEnv('_APP_VERSION', 'UNKNOWN'),
@@ -88,16 +90,17 @@ class Webhooks extends Action
$ch,
CURLOPT_HTTPHEADER,
[
'Content-Type: application/json',
'Content-Length: ' . \strlen($payload),
'X-' . APP_NAME . '-Webhook-Id: ' . $webhook->getId(),
'X-' . APP_NAME . '-Webhook-Events: ' . implode(',', $events),
'X-' . APP_NAME . '-Webhook-Name: ' . $webhook->getAttribute('name', ''),
'X-' . APP_NAME . '-Webhook-User-Id: ' . $user->getId(),
'X-' . APP_NAME . '-Webhook-Project-Id: ' . $project->getId(),
'X-' . APP_NAME . '-Webhook-Signature: ' . $signature,
'Content-Type: application/json',
'Content-Length: ' . \strlen($payload),
'X-' . APP_NAME . '-Webhook-Id: ' . $webhook->getId(),
'X-' . APP_NAME . '-Webhook-Events: ' . implode(',', $events),
'X-' . APP_NAME . '-Webhook-Name: ' . $webhook->getAttribute('name', ''),
'X-' . APP_NAME . '-Webhook-User-Id: ' . $user->getId(),
'X-' . APP_NAME . '-Webhook-Project-Id: ' . $project->getId(),
'X-' . APP_NAME . '-Webhook-Signature: ' . $signature,
]
);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
if (!$webhook->getAttribute('security', true)) {
\curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+28 -25
View File
@@ -8,6 +8,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use InfluxDB\Database as InfluxDatabase;
use DateTime;
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
class TimeSeries extends Calculator
@@ -426,32 +427,34 @@ class TimeSeries extends Calculator
$project = $this->database->getDocument('projects', $projectId);
$database = call_user_func($this->getProjectDB, $project);
try {
$document = $database->getDocument('stats', $id);
if ($document->isEmpty()) {
$database->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $metric,
'value' => $value,
'type' => $type,
'region' => $this->region,
]));
} else {
$database->updateDocument(
'stats',
$document->getId(),
$document->setAttribute('value', $value)
);
Authorization::skip(function () use ($database, $id, $period, $time, $metric, $value, $type, $projectId) {
try {
$document = $database->getDocument('stats', $id);
if ($document->isEmpty()) {
$database->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $metric,
'value' => $value,
'type' => $type,
'region' => $this->region,
]));
} else {
$database->updateDocument(
'stats',
$document->getId(),
$document->setAttribute('value', $value)
);
}
} catch (\Exception $e) { // if projects are deleted this might fail
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
} else {
throw $e;
}
}
} catch (\Exception $e) { // if projects are deleted this might fail
if (is_callable($this->errorHandler)) {
call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}");
} else {
throw $e;
}
}
});
$this->register->get('pools')->reclaim();
}
@@ -0,0 +1,28 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Messages extends Base
{
public const ALLOWED_ATTRIBUTES = [
'topics',
'users',
'targets',
'providerId',
'deliveredAt',
'deliveredTo',
'deliveryErrors',
'status',
'description',
'data'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('messages', self::ALLOWED_ATTRIBUTES);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Providers extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'provider',
'type',
'enabled',
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('providers', self::ALLOWED_ATTRIBUTES);
}
}
@@ -0,0 +1,20 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Subscribers extends Base
{
public const ALLOWED_ATTRIBUTES = [
'targetId',
'topicId'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('subscribers', self::ALLOWED_ATTRIBUTES);
}
}
@@ -0,0 +1,21 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Targets extends Base
{
public const ALLOWED_ATTRIBUTES = [
'userId',
'providerId',
'identifier',
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('targets', self::ALLOWED_ATTRIBUTES);
}
}
@@ -0,0 +1,20 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Topics extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'description',
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('topics', self::ALLOWED_ATTRIBUTES);
}
}
@@ -9,6 +9,7 @@ class Users extends Base
'email',
'phone',
'status',
'hash',
'passwordUpdate',
'registration',
'emailVerification',
+42
View File
@@ -95,4 +95,46 @@ class Request extends UtopiaRequest
{
return self::$route != null;
}
/**
* Get headers
*
* Method for getting all HTTP header parameters, including cookies.
*
* @return array<string,mixed>
*/
public function getHeaders(): array
{
$headers = $this->generateHeaders();
if (empty($this->swoole->cookie)) {
return $headers;
}
$cookieHeaders = [];
foreach ($this->swoole->cookie as $key => $value) {
$cookieHeaders[] = "{$key}={$value}";
}
if (!empty($cookieHeaders)) {
$headers['cookie'] = \implode('; ', $cookieHeaders);
}
return $headers;
}
/**
* Get header
*
* Method for querying HTTP header parameters. If $key is not found $default value will be returned.
*
* @param string $key
* @param string $default
* @return string
*/
public function getHeader(string $key, string $default = ''): string
{
$headers = $this->getHeaders();
return $headers[$key] ?? $default;
}
}
+31 -4
View File
@@ -31,6 +31,7 @@ use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\AttributeDatetime;
use Appwrite\Utopia\Response\Model\AttributeRelationship;
use Appwrite\Utopia\Response\Model\AuthProvider;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Branch;
use Appwrite\Utopia\Response\Model\Collection;
@@ -80,8 +81,12 @@ use Appwrite\Utopia\Response\Model\HealthVersion;
use Appwrite\Utopia\Response\Model\Installation;
use Appwrite\Utopia\Response\Model\LocaleCode;
use Appwrite\Utopia\Response\Model\Provider;
use Appwrite\Utopia\Response\Model\Message;
use Appwrite\Utopia\Response\Model\Subscriber;
use Appwrite\Utopia\Response\Model\Topic;
use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Target;
use Appwrite\Utopia\Response\Model\TemplateSMS;
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
@@ -191,6 +196,18 @@ class Response extends SwooleResponse
public const MODEL_PHONE = 'phone';
public const MODEL_PHONE_LIST = 'phoneList';
// Messaging
public const MODEL_PROVIDER = 'provider';
public const MODEL_PROVIDER_LIST = 'providerList';
public const MODEL_MESSAGE = 'message';
public const MODEL_MESSAGE_LIST = 'messageList';
public const MODEL_TOPIC = 'topic';
public const MODEL_TOPIC_LIST = 'topicList';
public const MODEL_SUBSCRIBER = 'subscriber';
public const MODEL_SUBSCRIBER_LIST = 'subscriberList';
public const MODEL_TARGET = 'target';
public const MODEL_TARGET_LIST = 'targetList';
// Teams
public const MODEL_TEAM = 'team';
public const MODEL_TEAM_LIST = 'teamList';
@@ -238,8 +255,8 @@ class Response extends SwooleResponse
public const MODEL_WEBHOOK_LIST = 'webhookList';
public const MODEL_KEY = 'key';
public const MODEL_KEY_LIST = 'keyList';
public const MODEL_PROVIDER = 'provider';
public const MODEL_PROVIDER_LIST = 'providerList';
public const MODEL_AUTH_PROVIDER = 'authProvider';
public const MODEL_AUTH_PROVIDER_LIST = 'authProviderList';
public const MODEL_PLATFORM = 'platform';
public const MODEL_PLATFORM_LIST = 'platformList';
public const MODEL_VARIABLE = 'variable';
@@ -316,7 +333,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false))
->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false))
->setModel(new BaseList('Providers List', self::MODEL_PROVIDER_LIST, 'platforms', self::MODEL_PROVIDER, true, false))
->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false))
->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false))
->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY))
->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT))
@@ -328,6 +345,11 @@ class Response extends SwooleResponse
->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS))
->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE))
->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE))
->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER))
->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE))
->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC))
->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER))
->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET))
->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION))
->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT))
// Entities
@@ -380,7 +402,7 @@ class Response extends SwooleResponse
->setModel(new Project())
->setModel(new Webhook())
->setModel(new Key())
->setModel(new Provider())
->setModel(new AuthProvider())
->setModel(new Platform())
->setModel(new Variable())
->setModel(new Country())
@@ -408,6 +430,11 @@ class Response extends SwooleResponse
->setModel(new TemplateSMS())
->setModel(new TemplateEmail())
->setModel(new ConsoleVariables())
->setModel(new Provider())
->setModel(new Message())
->setModel(new Topic())
->setModel(new Subscriber())
->setModel(new Target())
->setModel(new Migration())
->setModel(new MigrationReport())
->setModel(new MigrationFirebaseProject())
+2 -2
View File
@@ -60,8 +60,8 @@ class V13 extends Filter
protected function parseProject($content)
{
$content['providers'] = $content['authProviders'];
unset($content['authProviders']);
$content['providers'] = $content['oAuthProviders'];
unset($content['oAuthProviders']);
return $content;
}
+3 -3
View File
@@ -88,9 +88,9 @@ class V16 extends Filter
protected function parseProject(array $content)
{
foreach ($content['providers'] ?? [] as $i => $provider) {
$content['providers'][$i]['name'] = \ucfirst($provider['key']);
unset($content['providers'][$i]['key']);
foreach ($content['oAuthProviders'] ?? [] as $i => $provider) {
$content['oAuthProviders'][$i]['name'] = \ucfirst($provider['key']);
unset($content['oAuthProviders'][$i]['key']);
}
$content['domains'] = [];
@@ -0,0 +1,69 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class AuthProvider extends Model
{
/**
* @var bool
*/
protected bool $public = false;
public function __construct()
{
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Auth Provider.',
'default' => '',
'example' => 'github',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Auth Provider name.',
'default' => '',
'example' => 'GitHub',
])
->addRule('appId', [
'type' => self::TYPE_STRING,
'description' => 'OAuth 2.0 application ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('secret', [
'type' => self::TYPE_STRING,
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
'default' => '',
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Auth Provider is active and can be used to create session.',
'example' => '',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AuthProvider';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_AUTH_PROVIDER;
}
}
@@ -0,0 +1,131 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\DateTime;
use Utopia\Database\Document as DatabaseDocument;
class Message extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Message ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Message creation time in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Message update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('providerType', [
'type' => self::TYPE_STRING,
'description' => 'Message provider type.',
'default' => '',
'example' => MESSAGE_TYPE_EMAIL,
])
->addRule('topics', [
'type' => self::TYPE_STRING,
'description' => 'Topic IDs set as recipients.',
'default' => '',
'array' => true,
'example' => ['5e5ea5c16897e'],
])
->addRule('users', [
'type' => self::TYPE_STRING,
'description' => 'User IDs set as recipients.',
'default' => '',
'array' => true,
'example' => ['5e5ea5c16897e'],
])
->addRule('targets', [
'type' => self::TYPE_STRING,
'description' => 'Target IDs set as recipients.',
'default' => '',
'array' => true,
'example' => ['5e5ea5c16897e'],
])
->addRule('scheduledAt', [
'type' => self::TYPE_DATETIME,
'description' => 'The scheduled time for message.',
'required' => false,
'default' => DateTime::now(),
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('deliveredAt', [
'type' => self::TYPE_DATETIME,
'description' => 'The time when the message was delivered.',
'required' => false,
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('deliveryErrors', [
'type' => self::TYPE_STRING,
'description' => 'Delivery errors if any.',
'required' => false,
'default' => '',
'array' => true,
'example' => ['Failed to send message to target 5e5ea5c16897e: Credentials not valid.'],
])
->addRule('deliveredTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Number of recipients the message was delivered to.',
'default' => 0,
'example' => 1,
])
->addRule('data', [
'type' => self::TYPE_JSON,
'description' => 'Data of the message.',
'default' => [],
'example' => [
'subject' => 'Welcome to Appwrite',
'content' => 'Hi there, welcome to Appwrite family.',
],
])
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'Status of delivery.',
'default' => 'processing',
'example' => 'Message status can be one of the following: processing, sent, failed.',
])
->addRule('description', [
'type' => self::TYPE_STRING,
'description' => 'Message description.',
'required' => false,
'default' => '',
'example' => 'Welcome Email.',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Message';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_MESSAGE;
}
}
+50 -13
View File
@@ -132,15 +132,51 @@ class Project extends Model
'default' => false,
'example' => true,
])
->addRule('authPasswordDictionaryLength', [
'type' => self::TYPE_STRING,
'description' => 'How many most commonly used password to check against. Possible values are: 10k, 100k, 1m, 10m',
'default' => '10k',
'example' => '1m',
])
->addRule('authPasswordAi', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to check user\'s password against against AI opinion',
'default' => false,
'example' => true,
])
->addRule('authNotify', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to send notification on new session creation.',
'default' => false,
'example' => true,
])
->addRule('authSessionRefresh', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not sessions are automatically extended to session duration on every request',
'default' => false,
'example' => true,
])
->addRule('authPasswordAi', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to check user\'s password against against AI opinion',
'default' => false,
'example' => true,
])
->addRule('authRenewal', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not sessions are automatically extended to session duration on every request',
'default' => false,
'example' => true,
])
->addRule('authPersonalDataCheck', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to check the user password for similarity with their personal data.',
'default' => false,
'example' => true,
])
->addRule('providers', [
'type' => Response::MODEL_PROVIDER,
'description' => 'List of Providers.',
->addRule('oAuthProviders', [
'type' => Response::MODEL_AUTH_PROVIDER,
'description' => 'List of Auth Providers.',
'default' => [],
'example' => [new \stdClass()],
'array' => true,
@@ -220,8 +256,7 @@ class Project extends Model
'description' => 'SMTP server secure protocol',
'default' => '',
'example' => 'tls',
])
;
]);
$services = Config::getParam('services', []);
$auth = Config::getParam('auth', []);
@@ -236,8 +271,7 @@ class Project extends Model
'description' => $name . ' auth method status',
'example' => true,
'default' => true,
])
;
]);
}
foreach ($services as $service) {
@@ -254,8 +288,7 @@ class Project extends Model
'description' => $name . ' service status',
'example' => true,
'default' => true,
])
;
]);
}
}
@@ -320,6 +353,10 @@ class Project extends Model
$document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT);
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
$document->setAttribute('authPasswordDictionaryLength', $authValues['passwordDictionaryLength'] ?? false);
$document->setAttribute('authPasswordAi', $authValues['passwordAi'] ?? false);
$document->setAttribute('authNotify', $authValues['notify'] ?? false);
$document->setAttribute('authRenewal', $authValues['renewal'] ?? false);
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
foreach ($auth as $index => $method) {
@@ -328,9 +365,9 @@ class Project extends Model
$document->setAttribute('auth' . ucfirst($key), $value);
}
// Providers
$providers = Config::getParam('providers', []);
$providerValues = $document->getAttribute('authProviders', []);
// OAuth Providers
$providers = Config::getParam('oAuthProviders', []);
$providerValues = $document->getAttribute('oAuthProviders', []);
$projectProviders = [];
foreach ($providers as $key => $provider) {
@@ -348,7 +385,7 @@ class Project extends Model
]);
}
$document->setAttribute("providers", $projectProviders);
$document->setAttribute('oAuthProviders', $projectProviders);
return $document;
}
+46 -22
View File
@@ -7,44 +7,68 @@ use Appwrite\Utopia\Response\Model;
class Provider extends Model
{
/**
* @var bool
*/
protected bool $public = false;
public function __construct()
{
$this
->addRule('key', [
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Provider.',
'description' => 'Provider ID.',
'default' => '',
'example' => 'github',
'example' => '5e5ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Provider creation time in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Provider update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Provider name.',
'description' => 'The name for the provider instance.',
'default' => '',
'example' => 'GitHub',
'example' => 'Mailgun',
])
->addRule('appId', [
->addRule('provider', [
'type' => self::TYPE_STRING,
'description' => 'OAuth 2.0 application ID.',
'description' => 'The name of the provider service.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('secret', [
'type' => self::TYPE_STRING,
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
'default' => '',
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
'example' => 'mailgun',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Provider is active and can be used to create session.',
'example' => '',
'description' => 'Is provider enabled?',
'default' => true,
'example' => true,
])
;
->addRule('type', [
'type' => self::TYPE_STRING,
'description' => 'Type of provider.',
'default' => '',
'example' => MESSAGE_TYPE_SMS,
])
->addRule('credentials', [
'type' => self::TYPE_JSON,
'description' => 'Provider credentials.',
'default' => [],
'example' => [
'key' => '123456789'
],
])
->addRule('options', [
'type' => self::TYPE_JSON,
'description' => 'Provider options.',
'default' => [],
'required' => false,
'example' => [
'from' => 'sender-email@mydomain'
],
]);
}
/**
@@ -0,0 +1,85 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Subscriber extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Subscriber ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Subscriber creation time in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Subscriber update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('targetId', [
'type' => self::TYPE_STRING,
'description' => 'Target ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('target', [
'type' => Response::MODEL_TARGET,
'description' => 'Target.',
'default' => [],
'example' => [
'$id' => '259125845563242502',
'$createdAt' => self::TYPE_DATETIME_EXAMPLE,
'$updatedAt' => self::TYPE_DATETIME_EXAMPLE,
'providerType' => 'email',
'providerId' => '259125845563242502',
'name' => 'ageon-app-email',
'identifier' => 'random-mail@email.org',
'userId' => '5e5ea5c16897e',
],
])
->addRule('userName', [
'type' => self::TYPE_STRING,
'description' => 'User Name.',
'default' => '',
'example' => 'Aegon Targaryen',
])
->addRule('topicId', [
'type' => self::TYPE_STRING,
'description' => 'Topic ID.',
'default' => '',
'example' => '259125845563242502',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Subscriber';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_SUBSCRIBER;
}
}
@@ -0,0 +1,83 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Target extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Target ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Target creation time in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Target update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Target Name.',
'default' => '',
'example' => 'Aegon apple token',
])
->addRule('userId', [
'type' => self::TYPE_STRING,
'description' => 'User ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('providerId', [
'type' => self::TYPE_STRING,
'description' => 'Provider ID.',
'required' => false,
'default' => '',
'example' => '259125845563242502',
])
->addRule('providerType', [
'type' => self::TYPE_STRING,
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
'default' => '',
'example' => 'email',
])
->addRule('identifier', [
'type' => self::TYPE_STRING,
'description' => 'The target identifier.',
'default' => '',
'example' => 'token',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Target';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_TARGET;
}
}
@@ -40,6 +40,12 @@ class Token extends Model
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('securityPhrase', [
'type' => self::TYPE_STRING,
'description' => 'Token security phrase.',
'default' => '',
'example' => 'Tiger Whisper',
])
;
}
@@ -0,0 +1,71 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Topic extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Topic ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Topic creation time in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Topic update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'The name of the topic.',
'default' => '',
'example' => 'events',
])
->addRule('total', [
'type' => self::TYPE_INTEGER,
'description' => 'Total count of subscribers subscribed to topic.',
'default' => 0,
'example' => 100,
])
->addRule('description', [
'type' => self::TYPE_STRING,
'description' => 'Description of the topic.',
'default' => '',
'required' => false,
'example' => 'All events related messages will be sent to this topic.',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Topic';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_TOPIC;
}
}
@@ -120,6 +120,13 @@ class User extends Model
'default' => new \stdClass(),
'example' => ['theme' => 'pink', 'timezone' => 'UTC'],
])
->addRule('targets', [
'type' => Response::MODEL_TARGET,
'description' => 'A user-owned message receiver. A single user may have multiple e.g. emails, phones, and a browser. Each target is registered with a single provider.',
'default' => [],
'array' => true,
'example' => [],
])
->addRule('accessedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_USER_ACCCESS / 60 / 60 . ' hours.',
+12
View File
@@ -81,6 +81,18 @@ trait ProjectCustom
'locale.read',
'avatars.read',
'health.read',
'rules.read',
'rules.write',
'targets.read',
'targets.write',
'providers.read',
'providers.write',
'messages.read',
'messages.write',
'topics.write',
'topics.read',
'subscribers.write',
'subscribers.read',
],
]);
+37 -3
View File
@@ -415,7 +415,7 @@ trait AccountBase
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'queries' => [ 'limit(1)' ],
'queries' => ['limit(1)'],
]);
$this->assertEquals($responseLimit['headers']['status-code'], 200);
@@ -432,7 +432,7 @@ trait AccountBase
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'queries' => [ 'offset(1)' ],
'queries' => ['offset(1)'],
]);
$this->assertEquals($responseOffset['headers']['status-code'], 200);
@@ -449,7 +449,7 @@ trait AccountBase
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'queries' => [ 'limit(1)', 'offset(1)' ],
'queries' => ['limit(1)', 'offset(1)'],
]);
$this->assertEquals($responseLimitOffset['headers']['status-code'], 200);
@@ -945,6 +945,32 @@ trait AccountBase
return $data;
}
/**
* @depends testUpdateAccountVerification
*/
public function testCreateAccountVerificationForVerifiedEmail($data): array
{
$email = $data['email'] ?? '';
$name = $data['name'] ?? '';
$session = $data['session'] ?? '';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'url' => 'http://localhost/verification',
]);
$this->assertEquals(409, $response['headers']['status-code']);
return $data;
}
/**
* @depends testUpdateAccountVerification
*/
@@ -1089,6 +1115,14 @@ trait AccountBase
$this->assertEquals($response['headers']['status-code'], 204);
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]));
$this->assertEquals($response['headers']['status-code'], 401);
/**
* Test for FAILURE
*/
@@ -2,16 +2,16 @@
namespace Tests\E2E\Services\Account;
use Appwrite\Extend\Exception;
use Appwrite\SMS\Adapter\Mock;
use Appwrite\Tests\Retry;
use Tests\E2E\Client;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\SideClient;
use Utopia\App;
use Utopia\Database\DateTime;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\DSN\DSN;
use function sleep;
@@ -764,6 +764,7 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
$userId = $response['body']['userId'];
$messageId = $response['body']['$id'];
/**
* Test for FAILURE
@@ -1068,4 +1069,27 @@ class AccountCustomClientTest extends Scope
return $data;
}
/**
* @depends testPhoneVerification
*/
#[Retry(count: 1)]
public function testPhoneVerificationForVerifiedPhone(array $data): array
{
$session = $data['session'] ?? '';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]));
$this->assertEquals(409, $response['headers']['status-code']);
return $data;
}
}
@@ -1344,4 +1344,192 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(204, $response['headers']['status-code']);
}
public function testCookieExecution()
{
$timeout = 5;
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
$this->packageCode('php-cookie');
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'timeout' => $timeout,
]);
$functionId = $function['body']['$id'] ?? '';
$this->assertEquals(201, $function['headers']['status-code']);
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
'activate' => true
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(202, $deployment['headers']['status-code']);
// Poll until deployment is built
while (true) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
if (
$deployment['headers']['status-code'] >= 400
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
) {
break;
}
\sleep(1);
}
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $deployment['headers']['status-code']);
// Wait a little for activation to finish
sleep(5);
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => false,
'headers' => [
'cookie' => $cookie
]
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertEquals($cookie, $execution['body']['responseBody']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
$this->assertEquals(204, $response['headers']['status-code']);
}
public function testFunctionsDomain()
{
$timeout = 5;
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
$this->packageCode('php-cookie');
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'timeout' => $timeout,
'execute' => ['any']
]);
$functionId = $function['body']['$id'] ?? '';
$this->assertEquals(201, $function['headers']['status-code']);
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [ 'equal("resourceId", "' . $functionId . '")', 'equal("resourceType", "function")' ]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$domain = $rules['body']['rules'][0]['domain'];
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
'activate' => true
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(202, $deployment['headers']['status-code']);
// Poll until deployment is built
while (true) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
if (
$deployment['headers']['status-code'] >= 400
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
) {
break;
}
\sleep(1);
}
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $deployment['headers']['status-code']);
// Wait a little for activation to finish
sleep(5);
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => $cookie
]));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($cookie, $response['body']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
$this->assertEquals(204, $response['headers']['status-code']);
}
}

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