Compare commits

..
Author SHA1 Message Date
Jake Barnby 7fc214afbe Update libs 2024-03-05 11:55:29 +01:00
Jake Barnby c768375e13 Set tenant for CLI scope db 2024-03-04 20:54:35 +01:00
Jake Barnby fcc5a747d1 Fix test 2024-03-04 20:26:11 +01:00
Jake Barnby 78e4c0611b Update deps 2024-03-04 20:02:45 +01:00
Jake Barnby 74a3d6531f Merge remote-tracking branch 'origin/refactor-usage-sn' into feat-isolation-modes
# Conflicts:
#	composer.json
#	composer.lock
#	src/Appwrite/Platform/Tasks/CalcTierStats.php
#	tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
2024-03-04 19:50:10 +01:00
Christy JacobandGitHub 45092a1049 Update docker-compose.yml 2024-03-01 22:10:25 +05:30
868bba52aa Update app/init.php
Co-authored-by: Torsten Dittmann <torsten.dittmann@googlemail.com>
2024-03-01 22:05:42 +05:30
Christy Jacob 0b4716721d chore: update composer 2024-03-01 16:16:29 +00:00
Christy Jacob 2ffcb9b07e Merge branch 'main' of https://github.com/appwrite/appwrite into refactor-usage-sn 2024-03-01 16:08:03 +00:00
Christy JacobandGitHub d91f6960f7 Merge pull request #7661 from appwrite/chore-usage-queue-health
usage/usage-dump queue health endpoints
2024-03-01 21:25:02 +05:30
Torsten DittmannandGitHub 3542d360b0 Merge pull request #7698 from appwrite/update-error-message
chore: update error types for create account endpoints
2024-03-01 14:03:49 +01:00
Torsten DittmannandGitHub 9a821e250f Merge pull request #7697 from appwrite/chore-sync-1.4.x-into-main
Chore sync 1.4.x into main
2024-03-01 13:59:33 +01:00
Christy Jacob d1715e3bf4 chore: update tests 2024-03-01 12:50:11 +00:00
Christy Jacob c8a3b709ee chore: update error types for create account endpoints 2024-03-01 07:37:31 +00:00
Steven NguyenandGitHub fed197a313 Merge branch '1.4.x' into chore-sync-1.4.x-into-main 2024-02-29 21:53:39 +00:00
Christy JacobandGitHub ec85ec9f2a Merge pull request #7692 from appwrite/deletes-queue-retry
adding limit to queue retry
2024-02-29 00:47:09 +05:30
shimon 1a59361200 adding limit to queue retry 2024-02-28 21:15:59 +02:00
shimon 658396b4a8 adding limit to queue retry 2024-02-28 21:09:10 +02:00
shimon 53438accce adding limit to queue retry 2024-02-28 21:01:42 +02:00
shimon 84f47dc1d2 adding limit to queue retry 2024-02-28 20:21:35 +02:00
Christy JacobandGitHub 1538f78c18 Merge pull request #7672 from appwrite/fix-error-code
Change status code from 503 to 403
2024-02-28 10:41:19 +05:30
Christy JacobandGitHub 6acf6adf99 Merge pull request #7674 from appwrite/feat-add-ssl-to-smtp
Allow ssl along with tls in custom smtp
2024-02-28 10:40:49 +05:30
Khushboo Verma af3d6b2308 Allow ssl along with tls in custom smtp 2024-02-26 20:40:41 +05:30
Khushboo Verma 260928942b Update tests 2024-02-26 17:58:57 +05:30
Khushboo Verma bbcb5e8a6e Change status code from 503 to 403 2024-02-26 17:41:19 +05:30
Christy JacobandGitHub 03d6eb7401 Merge pull request #7669 from appwrite/main
Sync main
2024-02-26 17:39:33 +05:30
Jake BarnbyandGitHub 821e323a40 Merge pull request #7648 from UtkarshAhuja2003/fix-7587-list-api-select
[Fix]: Remove internal attributes on select query
2024-02-26 18:33:36 +13:00
navjotNSKandTorsten Dittmann cab2233816 Formatting 2024-02-25 20:24:36 +01:00
navjotNSKandTorsten Dittmann 3a8c5a2e65 Default value added for name 2024-02-25 20:24:36 +01:00
Utkarsh Ahuja faeb9aa8d4 tests: internal id in query select 2024-02-25 14:22:12 +00:00
shimon 92dfe93033 Merge branch 'main' of github.com:appwrite/appwrite into chore-usage-queue-health 2024-02-25 10:32:02 +02:00
Torsten DittmannandGitHub 3a524c24d9 Merge pull request #7656 from appwrite/fix-function-id-2
Fix-function-id-2
2024-02-24 19:32:24 +01:00
Eldad Fux b78725e650 Hide error messages from HTML templates 2024-02-24 19:12:27 +01:00
Eldad Fux e7a847a417 Fixed ID class namespace 2024-02-24 19:01:00 +01:00
Christy JacobandGitHub 0ad44ceb94 Merge pull request #7654 from appwrite/lohanidamodar-patch-3
Fix return
2024-02-24 20:49:37 +05:30
Damodar LohaniandGitHub 9fe0a5a31b Update Mail.php 2024-02-24 21:03:18 +05:45
Christy JacobandGitHub 07f7271c51 Merge pull request #7653 from appwrite/fix-mail-reset-attachment
Fix mail reset attachment
2024-02-24 20:29:41 +05:30
Damodar Lohani d91a0e3048 reset variables 2024-02-24 14:56:53 +00:00
Damodar Lohani ea484e593b format 2024-02-24 14:49:17 +00:00
Damodar Lohani ca1c83c2cf method to reset attachment 2024-02-24 14:33:38 +00:00
Utkarsh Ahuja 8c4473932f fix: list api select query 2024-02-24 10:55:33 +00:00
Christy JacobandGitHub d5c24df28d Merge pull request #7643 from appwrite/fix-storage-health-check
Fix content-type of file
2024-02-24 01:30:16 +05:30
Khushboo Verma 141cdf4c82 Fix content-type of file 2024-02-23 20:10:25 +05:30
Christy JacobandGitHub f79a310965 Merge pull request #7622 from appwrite/feat-publish-arm64
Add ARM64 to docker publish
2024-02-23 18:59:18 +05:30
Christy JacobandGitHub 65d99f8f90 Merge pull request #7632 from appwrite/revert-7630-revert-7627-fix-double-proxy
Fix: Router CURL
2024-02-22 15:17:12 +05:30
Christy JacobandGitHub c04f54da64 Revert "Revert "Fix: Router CURL"" 2024-02-22 15:10:18 +05:30
Christy JacobandGitHub 17888cc02d Merge pull request #7630 from appwrite/revert-7627-fix-double-proxy
Revert "Fix: Router CURL"
2024-02-22 12:27:36 +05:30
Christy JacobandGitHub d2787c8555 Revert "Fix: Router CURL" 2024-02-22 12:26:01 +05:30
Christy JacobandGitHub 42ca924557 Merge pull request #7629 from appwrite/revert-7614-chore-usage-queue-health
Revert "usage/usage-dump queue health endpoints"
2024-02-22 12:24:55 +05:30
Christy JacobandGitHub a8098831a3 Revert "usage/usage-dump queue health endpoints" 2024-02-22 12:21:48 +05:30
Christy JacobandGitHub 3f5e40ad46 Merge pull request #7591 from appwrite/feat-storage-health-check
Add storage health check
2024-02-22 12:16:10 +05:30
Eldad A. FuxandGitHub 10d882ce50 Merge pull request #7627 from appwrite/fix-double-proxy
Fix: Router CURL
2024-02-21 20:07:57 +01:00
Bradley Schofield 7a5d64a85f Merge branch 'refactor-usage-sn' into fix-double-proxy 2024-02-21 18:24:44 +00:00
Matej Bačo b03783c588 Fix bigs until works 2024-02-21 18:14:24 +01:00
Matej Bačo 6567cff5dc Fix missing todo 2024-02-21 17:03:48 +00:00
Shimon NewmanandGitHub db5c44c843 Merge pull request #7626 from appwrite/fix-msg91
fix: msg91
2024-02-21 18:41:27 +02:00
loks0n b2e1c162d2 chore: fmt 2024-02-21 16:27:37 +00:00
loks0n e3c261b9e4 fix: msg91 2024-02-21 16:24:55 +00:00
Matej Bačo 0dd2889de5 Fix double router curl 2024-02-21 14:51:21 +00:00
Bradley Schofield cc8705253e Add Docker Buildx for multiplatform 2024-02-21 10:12:20 +00:00
Eldad A. FuxandGitHub 14e81c2e27 Merge pull request #6826 from GuptaPratik02/doc-sdks-python-update-getting-started-guide
Update GETTING_STARTED.md
2024-02-20 17:50:41 +01:00
Bradley Schofield d60e7c641f Add ARM64 to docker publish 2024-02-20 15:56:25 +00:00
Vincent (Wen Yu) GeandGitHub f287e60e87 Merge branch 'main' into doc-sdks-python-update-getting-started-guide 2024-02-20 10:25:16 -05:00
Shimon NewmanandGitHub 3f7d35065e Merge pull request #7614 from appwrite/chore-usage-queue-health
usage/usage-dump queue health endpoints
2024-02-20 09:59:52 +02:00
Christy JacobandGitHub abc67ab0c6 Merge pull request #7615 from appwrite/fix-project-usage
usage logs updates
2024-02-20 09:26:03 +05:30
Damodar LohaniandGitHub c2ae330888 fix bandwidth usage 2024-02-20 09:14:48 +05:45
shimon 1577be9172 usage/usage-dump queue health endpoints 2024-02-19 21:36:14 +02:00
Matej Bačo 505b481537 Revert "Add smarter traefik load balancing with health check"
This reverts commit 2ab6156db4.
2024-02-19 14:10:35 +00:00
Matej Bačo 2ab6156db4 Add smarter traefik load balancing with health check 2024-02-19 14:08:15 +00:00
Eldad A. FuxandGitHub 0f7c73c1ab Merge pull request #7608 from appwrite/fix-project-usage
fix project network usage
2024-02-19 08:58:29 +01:00
Damodar Lohani 928ae74ffe fix project network usage 2024-02-19 06:04:29 +00:00
Khushboo Verma e4a86d9777 Remove global function 2024-02-19 09:19:54 +05:30
Eldad A. FuxandGitHub 44a9aed48e Merge pull request #7165 from appwrite/feat-more-phone-validation
Feat more phone validation
2024-02-18 22:39:45 +01:00
Eldad A. FuxandGitHub ad78259c10 Merge pull request #7375 from appwrite/fix-sentry-errors
Update exception for github session not found
2024-02-18 22:37:00 +01:00
Eldad A. FuxandGitHub 2c4a9e5ea9 Merge pull request #7566 from appwrite/chore-update-image-lib
Update image lib
2024-02-18 21:47:10 +01:00
Khushboo Verma 3b1233eaa9 Add deviceFunctions and deviceBuilds as well 2024-02-18 18:28:41 +05:30
Eldad A. FuxandGitHub 2a5df03c89 Merge pull request #7583 from appwrite/dev-introduce-redis-insights
dev: introduce redis insights
2024-02-18 06:46:35 +01:00
Christy JacobandGitHub c3ddc6a05d Merge pull request #5616 from appwrite/refactor-cache-poc
Refactor cache
2024-02-15 21:00:16 +05:30
Christy JacobandGitHub db792999c1 Merge pull request #7598 from appwrite/projectId-to-logger-2
Project id to logger
2024-02-15 20:46:14 +05:30
shimon ed4319314a Addressed comments 2024-02-15 17:15:10 +02:00
shimon 2a5f21e982 Addressed comments 2024-02-15 17:12:37 +02:00
Christy JacobandGitHub 7dbb1c6399 Merge branch 'main' into refactor-cache-poc 2024-02-15 20:33:57 +05:30
Khushboo Verma 00db19257d Add health storage check 2024-02-15 11:57:24 +05:30
Christy JacobandGitHub f0a4cf78ab Merge pull request #7588 from appwrite/feat-usage-log
usage logs updates
2024-02-15 02:41:19 +05:30
shimon dae01f7077 usage logs updates 2024-02-14 23:07:25 +02:00
shimon 26af9b7463 usage logs updates 2024-02-14 23:05:01 +02:00
shimon 459db9b43e usage updates 2024-02-14 07:34:12 +02:00
Torsten Dittmann 3476ab2627 dev: introduce redis insights 2024-02-13 12:26:02 +00:00
Jake BarnbyandGitHub cbf1085850 Merge pull request #7135 from appwrite/remove-deletes-db-leftovers
Remove database methods from deletes worker
2024-02-13 23:13:24 +13:00
shimon dac0e2a543 Merge branch 'main' of github.com:appwrite/appwrite into refactor-cache-poc
 Conflicts:
	src/Appwrite/Platform/Workers/UsageHook.php
2024-02-13 10:48:47 +02:00
Christy JacobandGitHub c076dfe6f1 Merge pull request #7576 from appwrite/refactor-usage-sn
Refactor usage sn
2024-02-12 23:05:56 +05:30
Jake BarnbyandGitHub f5aebe6673 Merge pull request #7559 from Souptik2001/fix-7109-user-activity-log
Fix missing user activity logs
2024-02-12 23:04:20 +13:00
shimon 70effd1e99 Sync against main 2024-02-12 11:14:26 +02:00
shimon e7ace61273 Sync against main 2024-02-12 11:10:52 +02:00
shimon 6b6f04d2d3 Merge branch 'main' of github.com:appwrite/appwrite into refactor-cache-poc
 Conflicts:
	app/config/collections.php
	app/controllers/shared/api.php
2024-02-12 10:55:53 +02:00
Jake Barnby be761b31c1 Update image lib 2024-02-12 14:21:06 +13:00
Eldad A. FuxandGitHub eb75b527ad Merge pull request #7560 from appwrite/update-x-example-format
Update place holders from [PARAM_NAME] to <PARAM_NAME>
2024-02-11 23:04:42 +01:00
Damodar LohaniandGitHub d4ad369a7e Merge pull request #7563 from appwrite/fix-user-deletion
PEA-233-prevent console user deletion before deleting their team
2024-02-11 16:04:59 +05:45
Damodar Lohani eb7e2fd23b fix injection 2024-02-11 09:46:39 +00:00
Damodar Lohani 36eabfa2dd fix check twice 2024-02-11 09:45:36 +00:00
Damodar Lohani 2634c928c7 fix format 2024-02-11 09:34:13 +00:00
Damodar Lohani e462d73f65 prevent user with active team, from deletion 2024-02-11 09:31:09 +00:00
Christy JacobandGitHub e2baefe8c6 Merge pull request #7500 from appwrite/fix-account-delete
PEA-233-prevent console user deletion before deleting their team
2024-02-11 14:52:24 +05:30
Damodar LohaniandGitHub 4ffde8e827 fix indentation 2024-02-11 14:55:14 +05:45
Damodar LohaniandGitHub 57a695367b fix typo 2024-02-11 14:54:32 +05:45
Damodar LohaniandGitHub 681688e252 fix rename 2024-02-11 14:52:54 +05:45
Damodar LohaniandGitHub a8718bccb5 fix rename 2024-02-11 14:42:21 +05:45
eba03cb126 Update app/config/errors.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-02-11 14:41:32 +05:45
622bdb7b2b Update app/config/errors.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-02-11 14:41:25 +05:45
c8720805c7 Update app/controllers/api/account.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-02-11 14:41:19 +05:45
Christy JacobandGitHub fdb11af6bb Merge pull request #7561 from appwrite/update-team-counts
Update team counts
2024-02-11 12:15:05 +05:30
shimon b09386727b Usage logs text update 2024-02-10 10:34:44 +02:00
Christy Jacob f3e8994bb8 fix: use atomic operations for updating team count 2024-02-09 23:21:17 +05:30
Christy Jacob fb68dcc89e fix: use atomic operations for updating team count 2024-02-09 23:20:37 +05:30
Christy Jacob 5abcb86fd7 fix: use atomic operations for updating team count 2024-02-09 23:18:18 +05:30
Christy JacobandGitHub ff93957922 Merge pull request #7503 from appwrite/usage-queue-poc
Usage queue poc
2024-02-09 20:12:40 +05:30
shimon 179a045399 Merge branch 'refactor-usage-sn' of github.com:appwrite/appwrite into usage-queue-poc
 Conflicts:
	composer.lock
2024-02-09 16:29:26 +02:00
shimon d0985db27d interval update 2024-02-09 13:22:48 +02:00
shimon 3788d5bceb interval update 2024-02-09 12:56:37 +02:00
Vincent (Wen Yu) Ge f785ffbd71 Update place holders from [PARAM_NAME] to <PARAM_NAME> 2024-02-09 03:06:38 +00:00
shimon c1fb4ecf39 interval update 2024-02-08 21:41:23 +02:00
Souptik Datta e6a97bb36a Merge branch 'master' into fix-7109-user-activity-log 2024-02-09 00:40:36 +05:30
shimon a1539395c6 interval update 2024-02-08 21:08:29 +02:00
Christy JacobandGitHub 94468aa28f Merge pull request #7556 from appwrite/refactor-usage-sn
Sync main with refactor-usage-sn
2024-02-09 00:25:07 +05:30
shimon 72ee7d7f34 interval update 2024-02-08 20:51:54 +02:00
Souptik Datta 8d7be399d3 fix: Fix project user activity log's missing fields and total count
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
2024-02-09 00:15:48 +05:30
Souptik Datta f42a78ef9c fix: Fix account activity logs total count
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
2024-02-09 00:14:37 +05:30
Christy Jacob 5842f1950a chore: linter 2024-02-08 18:02:35 +00:00
ba4f708137 Apply suggestions from code review
Co-authored-by: Torsten Dittmann <torsten.dittmann@googlemail.com>
2024-02-08 23:26:43 +05:30
Christy Jacob 2fdd0656d0 Merge branch 'main' of https://github.com/appwrite/appwrite into refactor-usage-sn 2024-02-08 11:48:53 +00:00
shimon 790cdc297b composer update 2024-02-08 10:26:45 +02:00
shimon 990698e423 composer update 2024-02-08 09:32:05 +02:00
shimon 632f78140f Merge branch 'refactor-usage-sn' of github.com:appwrite/appwrite into usage-queue-poc
 Conflicts:
	composer.json
	composer.lock
2024-02-08 09:29:12 +02:00
Christy JacobandGitHub 243ddd4b33 Merge pull request #7519 from appwrite/update-param-name
Update param name
2024-02-08 12:55:19 +05:30
shimon 93a99b561c remove debug lines 2024-02-07 11:54:18 +02:00
Christy JacobandGitHub 6a5defb91c Merge pull request #7520 from appwrite/feat-sms-metric
Add count for messages(sms) metric
2024-02-06 22:44:06 +05:30
Christy JacobandGitHub 2f93dcc4df Merge pull request #7547 from appwrite/feat-certificate-health-check
Add health certificate validity endpoint
2024-02-06 22:31:07 +05:30
Christy JacobandGitHub 603b8ebd8c Merge pull request #7544 from appwrite/fix-add-delay-to-maintenance-worker
Add a 20 second delay to maintenance worker
2024-02-06 22:28:47 +05:30
Christy JacobandGitHub 4dee937e36 Update app/config/variables.php 2024-02-06 22:26:47 +05:30
Christy JacobandGitHub bb92074093 Update .env 2024-02-06 22:26:40 +05:30
Khushboo Verma fb127270ce Add test for empty domain 2024-02-06 16:04:58 +05:30
Khushboo Verma 11ceeff5ca Add health certificate validity endpoint 2024-02-06 15:15:03 +05:30
Khushboo Verma 8ed5489d9c Added env variable to variables.php and compose.phtml 2024-02-05 16:11:37 +05:30
Khushboo Verma 1fefb7e133 Add a 20 second delay to maintenance worker 2024-02-05 15:01:23 +05:30
shimon 6a6c3445fa updates 2024-02-04 20:02:01 +02:00
Eldad A. FuxandGitHub 6fe725ff9d Merge pull request #7539 from appwrite/fix-client-error-reporting
Fix: client error reporting
2024-02-02 15:12:24 +01:00
Matej Bačo 4115537f72 Fix client error reporting 2024-02-02 14:33:52 +01:00
Jake BarnbyandGitHub 26b2c27eb0 Merge pull request #7531 from appwrite/remove-debug-sn
remove debug leftovers
2024-02-01 21:38:26 +13:00
shimon 62bc540db0 removed var_dump leftovers 2024-02-01 10:35:38 +02:00
shimon 1ef5c9bfcd removed var_dump leftovers 2024-02-01 10:32:40 +02:00
Christy JacobandGitHub b07bdf610a Merge pull request #7523 from appwrite/revert-rate-limit
chore: update rate limits
2024-01-30 19:33:54 +04:00
Christy Jacob 8d7705a2e3 chore: update rate limits 2024-01-30 15:28:48 +00:00
shimon bfe042dc3e fix 2024-01-30 13:29:15 +02:00
shimon 5518ca165e remarks 2024-01-30 13:24:57 +02:00
shimon 13a879c274 Add count for messages(sms) metric 2024-01-30 11:19:10 +02:00
Christy Jacob 7b5be4e154 chore: update team membership deletion 2024-01-30 08:42:46 +00:00
Christy Jacob 3c7fae7843 chore: update param name 2024-01-30 08:10:13 +00:00
Christy JacobandGitHub 742e80c0c6 Merge pull request #7515 from appwrite/update-auth-labels
chore: add auth label to phone endpoint
2024-01-30 00:53:49 +04:00
Matej Bačo 738a696ca9 Add proejct ID abuse protection 2024-01-29 20:38:01 +00:00
Christy Jacob 99c78738f5 Merge branch 'update-auth-labels' of https://github.com/appwrite/appwrite into update-auth-labels 2024-01-29 20:25:29 +00:00
Christy Jacob c192e48c48 chore: remove return 2024-01-29 20:25:23 +00:00
Matej Bačo 7acdaa5978 PR review changes 2024-01-29 20:24:21 +00:00
Matej Bačo a755d76f52 Merge branch 'update-auth-labels' of https://github.com/appwrite/appwrite into update-auth-labels 2024-01-29 20:11:54 +00:00
Matej Bačo bc88197e0d Add more abuse keys 2024-01-29 20:11:44 +00:00
Christy Jacob 1e966d6b2e chore: rename env variable 2024-01-29 20:09:23 +00:00
Christy Jacob 77c4c5d294 Merge branch 'update-auth-labels' of https://github.com/appwrite/appwrite into update-auth-labels 2024-01-29 20:07:42 +00:00
Christy Jacob 2ff7c5ac8e chore: rename env variable 2024-01-29 20:07:17 +00:00
Matej Bačo 7db740d9ea Merge branch 'update-auth-labels' of https://github.com/appwrite/appwrite into update-auth-labels 2024-01-29 20:03:25 +00:00
Matej Bačo 0a518cd47e Fix failing tests 2024-01-29 20:03:14 +00:00
Christy Jacob dc6f9e6d59 chore: add new env variable 2024-01-29 20:01:14 +00:00
Christy Jacob 47fc6efb0d chore: update endpoint 2024-01-29 17:48:12 +00:00
Christy Jacob 0ff6e99354 chore: update auth group 2024-01-29 17:42:14 +00:00
Christy Jacob af21b44125 chore: revert abuse key 2024-01-29 17:25:56 +00:00
Christy Jacob 62246b5a2c chore: add auth label to phone endpoint 2024-01-29 17:24:10 +00:00
Christy JacobandGitHub 5ff8fd98a7 Merge pull request #7514 from appwrite/add-logs
chore: add logs
2024-01-29 20:48:03 +04:00
Christy Jacob d849aa1ed2 chore: add logs 2024-01-29 16:47:12 +00:00
Christy Jacob a3b33ab746 chore: update var_dump 2024-01-29 15:56:07 +00:00
Christy JacobandGitHub 537bbdf419 Merge pull request #7513 from appwrite/fix-membership-count
chore: add logs
2024-01-29 19:11:08 +04:00
Christy Jacob 81f6e1990a chore: add logs 2024-01-29 15:07:56 +00:00
Christy JacobandGitHub 99bf0b8ff7 Merge pull request #7511 from appwrite/fix-membership-count
fix: use atomic operations for count updates
2024-01-29 17:40:59 +04:00
Christy Jacob 94c423c429 chore: add project details to messaging worker 2024-01-29 13:27:03 +00:00
Christy Jacob 6c09d04038 fix: use atomic operations for count updates 2024-01-29 10:54:58 +00:00
shimon 44ed6826a9 refactor usage poc 2024-01-28 15:33:04 +02:00
shimon 36fddd4156 refactor usage poc 2024-01-28 11:46:13 +02:00
shimon 7a2ee683e3 refactor usage poc 2024-01-28 11:28:59 +02:00
Damodar Lohani 359b3326d9 remove unused import 2024-01-28 03:03:20 +00:00
Damodar Lohani 7a94bd143c update account delete test 2024-01-28 03:02:44 +00:00
Damodar Lohani 9b7a5f55b7 fix linter 2024-01-28 03:01:34 +00:00
Damodar Lohani e5dfed0aa3 fix, check only for confirmed membership 2024-01-28 02:47:23 +00:00
Damodar Lohani 215a139e87 prevent console user deletion before deleting their team 2024-01-28 02:10:14 +00:00
Christy JacobandGitHub 5d2d74e64d Merge pull request #7499 from appwrite/feat-add-queue-threshold
Add threshold to queue failed health endpoint
2024-01-26 22:13:27 +04:00
Christy JacobandGitHub 906e227359 Merge pull request #7497 from appwrite/feat-queue-commands
Add queue management commands
2024-01-26 22:06:45 +04:00
Christy Jacob 91e625cfc2 chore: linter 2024-01-26 18:04:06 +00:00
Bradley Schofield 126c3a38de Add threshold 2024-01-26 15:55:55 +00:00
Bradley Schofield 6ceeacbe03 Run Linter 2024-01-26 15:52:58 +00:00
Bradley Schofield 186772cc15 Simplify commands, fix queue consts 2024-01-26 15:51:28 +00:00
Christy JacobandGitHub 7e444b037d Merge pull request #7482 from appwrite/feat-improve-cname-logging
Feat: CNAME validation logs
2024-01-26 19:08:26 +04:00
Christy JacobandGitHub 6d15ffa3ca Merge pull request #7466 from appwrite/feat-queue-failed-health-endpoints
Add failed queues endpoint to health
2024-01-26 17:36:52 +04:00
Christy JacobandGitHub 304b72a0c8 Merge pull request #7391 from appwrite/feat-impl-queue-retry
Add Queue Retry Command to Appwrite
2024-01-26 17:35:01 +04:00
Matej Bačo ecb48b389e FixCI/CD failures 2024-01-26 12:22:06 +00:00
Matej Bačo c0ae2e6549 PR review changes for exception publishing 2024-01-26 12:07:08 +00:00
Bradley Schofield 4c96f76762 Run Linter 2024-01-26 11:20:13 +00:00
Bradley Schofield 216a5d7001 Add queue management commands 2024-01-26 11:11:18 +00:00
Bradley Schofield 772d42cef8 Update health.php 2024-01-26 09:52:41 +00:00
Bradley Schofield e2c05fea8c Update health.php 2024-01-26 09:42:21 +00:00
Bradley Schofield 73b1837d66 Remove HealthFailedJobs Model 2024-01-26 09:41:30 +00:00
Matej Bačo 50c2ebc950 PR review changes 2024-01-25 14:56:15 +00:00
shimon b024cb5614 removed debug lines 2024-01-25 13:42:43 +02:00
47af19dd17 Update app/controllers/api/health.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-25 10:21:52 +00:00
Bradley Schofield 2b31c76918 Update DatabasesBase.php 2024-01-25 10:04:41 +00:00
Bradley Schofield 26d8278a89 Merge branch 'feat-impl-queue-retry' into feat-queue-failed-health-endpoints 2024-01-24 18:00:51 +00:00
Bradley Schofield 6997eec9dd Rename RetryJobs to QueueRetry 2024-01-24 17:54:52 +00:00
Bradley Schofield 192599d7c6 Merge branch 'main' into feat-impl-queue-retry 2024-01-24 17:54:29 +00:00
Bradley Schofield 57c019ff98 Update RetryJobs.php 2024-01-24 17:49:52 +00:00
Bradley Schofield 867bc0281c Update health.php 2024-01-24 17:48:27 +00:00
Bradley Schofield dc2107fe66 Merge branch 'feat-impl-queue-retry' into feat-queue-failed-health-endpoints 2024-01-24 17:47:11 +00:00
Bradley Schofield ab2d830cc0 Address comments 2024-01-24 17:44:29 +00:00
d3e9d03329 Update src/Appwrite/Platform/Tasks/RetryJobs.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-24 14:45:24 +00:00
351abe50d3 Update src/Appwrite/Platform/Services/Tasks.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-24 14:45:17 +00:00
Matej Bačo f4a4cfa207 PR review changes 2024-01-24 12:40:56 +00:00
Torsten DittmannandGitHub 47ea083ea4 Merge pull request #7005 from appwrite/refactor-usage-sn
Refactor usage
2024-01-24 10:00:07 +01:00
Jake BarnbyandGitHub 64fde6cddd Merge pull request #7464 from appwrite/fix-ruby-null-params
Update ruby
2024-01-24 18:50:47 +13:00
Matej Bačo 5bd106afaf Implement better cname validation logging 2024-01-23 15:40:32 +00:00
Bradley SchofieldandGitHub 448b3b0cc0 Merge branch 'main' into feat-impl-queue-retry 2024-01-22 16:51:18 +00:00
Bradley Schofield f3579b3f12 Address Christy's Comments 2024-01-22 16:50:45 +00:00
09a7fc9063 Update bin/queue-retry
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-22 13:51:20 +00:00
2a65dfcc5d Update Dockerfile
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-22 13:51:14 +00:00
shimon d220c2bdd7 Added marked_for_deletion and billing_plan_downgrade
to hamster export
2024-01-22 14:48:25 +02:00
Shimon NewmanandGitHub e9a13fe93c Hamster additions
Hamster additions
2024-01-22 11:31:09 +02:00
shimon 2b865bdf97 Added marked_for_deletion and billing_plan_downgrade
to hamster export
2024-01-22 11:29:11 +02:00
shimon 19b1de283b Merge branch 'refactor-usage-sn' of github.com:appwrite/appwrite into hamster-additions 2024-01-22 11:25:37 +02:00
德华 刘 d04f9f41c3 chore: refactor code 2024-01-22 08:30:48 +00:00
德华 刘 ff7523d6db chore: refactor code 2024-01-22 08:26:38 +00:00
shimon 9fc1b0b71b Added marked_for_deletion and billing_plan_downgrade
to hamster export
2024-01-22 10:09:13 +02:00
shimon 0e30f6533e Added marked_for_deletion and billing_plan_downgrade
to hamster export
2024-01-22 09:54:09 +02:00
shimon c116de29e1 Added marked_for_deletion and billing_plan_downgrade
to hamster export
2024-01-22 09:28:45 +02:00
shimon 370704fafd moved cache collection to $commonCollections collection config 2024-01-21 10:47:00 +02:00
Bradley Schofield 1e247bdde1 Address Christy's Comments and Update Lib 2024-01-19 13:18:37 +00:00
Jake Barnby 6629fdd87f Bump generator 2024-01-20 00:24:09 +13:00
shimon 37316f97ff adding debug output to usage-worker 2024-01-18 18:52:20 +02:00
Bradley Schofield d3b8ce704e Add failed queue endpoint 2024-01-18 16:18:49 +00:00
shimon a0b38a5a7c Merge branch 'main' of github.com:appwrite/appwrite into refactor-usage-sn 2024-01-18 14:19:19 +02:00
shimon 554bc89cb5 sync config::collections against main 2024-01-18 14:14:24 +02:00
shimon b08e3d4e7b sync config::collections against main 2024-01-18 14:12:00 +02:00
shimon 90cf28389c sync config::collections against main 2024-01-18 14:10:57 +02:00
shimon 8581df9c14 Merge branch 'main' of github.com:appwrite/appwrite into refactor-cache-poc 2024-01-18 14:05:58 +02:00
Jake Barnby de96e0f3dc Update ruby 2024-01-18 20:07:39 +13:00
shimon 6bd810cb3a hamster additions 2024-01-17 22:19:00 +02:00
shimon 0d106684ae hamster additions 2024-01-17 22:10:06 +02:00
e5245909e3 Update src/Appwrite/Platform/Workers/Hamster.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-17 22:09:17 +02:00
3f63b87ea6 Update src/Appwrite/Platform/Workers/Hamster.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-17 22:09:02 +02:00
c1ca623bab Update src/Appwrite/Platform/Workers/Hamster.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-17 22:08:44 +02:00
5d164e0592 Update src/Appwrite/Platform/Workers/Hamster.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-17 22:08:37 +02:00
a954cea8fc Update src/Appwrite/Platform/Workers/Hamster.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2024-01-17 22:08:29 +02:00
shimon f9d6da6672 hamster additions 2024-01-17 21:58:16 +02:00
shimon 40e17ad625 hamster additions 2024-01-17 21:53:12 +02:00
Bradley Schofield da014b0ad3 Rename retry to queue-retry 2024-01-17 15:49:50 +00:00
Christy JacobandGitHub be3e3baca0 Merge pull request #7443 from appwrite/fix-bandwidth-name
Rename usage_network_infinity to usage_bandwidth_infinity
2024-01-15 12:40:05 +04:00
Bradley Schofield 4cb48638be Rename usage_network_infinity to usage_bandwidth_infinity 2024-01-12 15:22:18 +00:00
Torsten DittmannandGitHub 78f7b1897e fix: hotfix for redirect param in custom templates (#7437)
* fix: hotfix for redirect param in custom templates
* fix: redirect param variables
* fix: variable typo
* chore: update comments for template variables
2024-01-11 21:36:05 +01:00
Christy JacobandGitHub bd920ead40 Merge pull request #7421 from appwrite/feat-update-assistant
feat: update assistant
2024-01-10 15:54:11 +04:00
loks0n 310d75b6be feat: update assistant 2024-01-10 10:17:52 +00:00
Christy JacobandGitHub a2d97b3b22 Merge pull request #7417 from appwrite/update-hamster
chore: update collection name in hamster
2024-01-09 16:52:10 +04:00
德华 刘 65c90c9475 chore: update collection name in hamster 2024-01-09 12:41:58 +00:00
Torsten DittmannandGitHub d344ba46c4 feat: account delete (#7415)
* feat: account delete

* chore: run specs

* release: console sdk

* chore: run formatter

* tests: fix project tests
2024-01-09 12:58:36 +01:00
Christy JacobandGitHub b403a13061 Merge pull request #7410 from appwrite/fix-incorrect-framework-version
Fix utopia-php/framework version
2024-01-09 12:31:13 +04:00
Eldad A. FuxandGitHub b0a8aac0b7 Merge pull request #7348 from appwrite/fix-delete-identity-event
Update the delete identity endpoints to set the params and payload
2024-01-09 07:52:08 +01:00
Steven Nguyen dea3c0416b Fix broken usage tests 2024-01-09 03:11:00 +00:00
Steven Nguyen 44182b4444 Fix incorrect response model 2024-01-09 03:10:43 +00:00
Steven Nguyen fc2a4e52e2 Update the usage test to retry since the test is flaky 2024-01-09 02:23:22 +00:00
Steven Nguyen 747779b245 Fix usage test checking wrong array key 2024-01-09 02:09:40 +00:00
Steven Nguyen a7048255e8 Fix utopia-php/framework version
The framework version needs to be 0.33.0 as a new param was added to Template->setParam()
that is used across Appwrite.
2024-01-09 01:36:07 +00:00
Eldad A. FuxandGitHub ef72e08386 Merge pull request #7271 from appwrite/feat-7223-install-without-start
Add a flag to install and upgrade commands to not start Appwrite
2024-01-08 22:09:22 +01:00
Steven Nguyen 864703fb4e Update the install/upgrade arguments to be kebab instead of camel case
Kebab case like --http-port is much more common in CLIs than --httpPort.
2024-01-08 20:56:51 +00:00
shimon 472712dbb7 update utopia-framework version 2024-01-08 19:30:27 +02:00
shimon dcc0aa6ea5 sync with main
and bump utopia-cache lib version
2024-01-08 19:21:46 +02:00
shimon deb189f44b sync with main
and bump utopia-cache lib version
2024-01-08 19:16:47 +02:00
shimon 8163eefe4d Merge branch 'main' of github.com:appwrite/appwrite into refactor-usage-sn 2024-01-08 19:16:10 +02:00
Torsten DittmannandGitHub 9ba2eafa83 fix: escape html in email params (#7409)
* fix: escape html in email params

* revert: phpunit stop on failure

* chore: fix linter
2024-01-08 18:08:17 +01:00
shimon 03f2041913 Merge branch 'main' of github.com:appwrite/appwrite into refactor-cache-poc 2024-01-07 18:09:48 +02:00
Matej Bačo fae28b468c Fix password hashing bug 2024-01-07 13:40:45 +01:00
d9c04f4fc0 Update src/Appwrite/Platform/Tasks/Retry.php
Co-authored-by: Eldad A. Fux <eldad.fux@gmail.com>
2024-01-05 12:37:03 +00:00
Matej Bačo e5f0e00742 Apply password validation to all places 2024-01-05 12:31:38 +01:00
Matej Bačo 0a2be34ceb Fix PHP refference bug 2024-01-05 12:07:41 +01:00
Matej Bačo ba32170f72 Improve password validator hook 2024-01-04 16:26:15 +01:00
Bradley Schofield 4199126984 Run Linter 2024-01-04 13:04:51 +00:00
Bradley Schofield 01d76a1746 Add Queue Retry Command to Appwrite 2024-01-04 13:00:25 +00:00
Ben HumphriesandGitHub fbd277f6aa executor: pass build timeout to runtimes (#7350)
open-runtimes executor (v1/runtimes) supports passing a timeout parameter that defaults to 600 seconds.

->param('timeout', 600, new Integer(), 'Commands execution time in seconds.', true)

https://github.com/open-runtimes/executor/blob/main/app/http.php#L383

This change passes the _APP_FUNCTIONS_BUILD_TIMEOUT env var.
2024-01-04 11:46:08 +01:00
Torsten DittmannandGitHub 99c163d217 Merge pull request #7387 from appwrite/fix-app-console-hostnames-check-on-refactor-usage-sn
Fix app console hostnames check on refactor usage sn
2024-01-03 22:22:39 +01:00
Steven Nguyen 0c3ed13ad3 Fix _APP_CONSOLE_HOSTNAMES check
Ensure invalid hostnames such as empty strings are not added as a
hostname.
2024-01-03 20:39:19 +00:00
Steven Nguyen d2d36bbfae Add changes from previous console platforms variable PR
See https://github.com/appwrite/appwrite/pull/4581
2024-01-03 20:38:42 +00:00
Torsten DittmannandGitHub 80f5ded0e3 Merge pull request #7386 from appwrite/feat-add-general-tests
Add General E2E tests to CI pipeline
2024-01-03 21:36:21 +01:00
Torsten DittmannandGitHub 6b1330e400 Merge pull request #7385 from appwrite/fix-app-console-hostnames-check
Fix app console hostnames check
2024-01-03 21:28:29 +01:00
Steven Nguyen b6b5570b0b Add General E2E tests to CI pipeline 2024-01-03 19:48:31 +00:00
Steven Nguyen faf39fcc81 Fix _APP_CONSOLE_HOSTNAMES check
Ensure invalid hostnames such as empty strings are not added as a
hostname.
2024-01-03 19:30:49 +00:00
Steven Nguyen 06e385b346 Add changes from previous console platforms variable PR
See https://github.com/appwrite/appwrite/pull/4581
2024-01-03 19:30:42 +00:00
Torsten DittmannandGitHub 127aa3cea7 Merge pull request #7377 from appwrite/feat-console-hostnames-on-refactor-usage-sn
Add `_APP_CONSOLE_HOSTNAMES` env var to allow more hostnames to the console project
2024-01-03 17:24:52 +01:00
Torsten DittmannandSteven Nguyen 0a17e2bce9 feat: add new env variable to compsoe files 2024-01-02 19:53:37 +00:00
Torsten DittmannandSteven Nguyen a29cef2334 feat: console hostname env variable 2024-01-02 19:53:20 +00:00
Torsten DittmannandGitHub 7a2f7f5a17 Merge pull request #7360 from appwrite/feat-console-hostnames
feat: console hostname env variable
2024-01-02 17:16:34 +01:00
Khushboo Verma 671b1624d5 Update exception for github session not found 2024-01-02 16:20:20 +05:30
shimon f14250bf2a sync with main 2024-01-02 12:24:57 +02:00
shimon 99f1543f1b Merge branch 'main' of github.com:appwrite/appwrite into refactor-usage-sn
 Conflicts:
	.gitmodules
	Dockerfile
	app/console
	app/init.php
	composer.lock
	src/Appwrite/Platform/Services/Workers.php
	src/Appwrite/Platform/Tasks/Hamster.php
2024-01-02 11:49:23 +02:00
Damodar LohaniandGitHub 3236e9eabe Merge pull request #7328 from appwrite/fix-chunk-upload-permission
Fix permission issue with chunk upload
2024-01-02 12:36:48 +05:45
Damodar LohaniandGitHub 879320e23e update comment regarding validation 2024-01-02 11:53:48 +05:45
Damodar LohaniandGitHub b2b863f09f Merge branch 'main' into fix-chunk-upload-permission 2024-01-02 11:51:09 +05:45
Damodar LohaniandGitHub d86ee91316 Merge pull request #7366 from appwrite/update-storage-compression-constants
PEA-38 compression constants
2023-12-31 17:47:40 +05:45
Damodar Lohani a2ad870084 use compression constant from utopia/storage 2023-12-31 11:46:45 +00:00
Damodar Lohani d070989f73 update utopia storage 2023-12-31 11:46:27 +00:00
Damodar Lohani c99976560d update utopia-storage 2023-12-31 11:37:22 +00:00
Eldad A. FuxandGitHub f5705df02c Merge pull request #7351 from appwrite/pea-33-improve-large-files-handling
Improve large file handling
2023-12-31 08:48:32 +01:00
Eldad A. FuxandGitHub 4f7b1748f4 Merge pull request #7276 from fanksin/patch-1
Update tr.json
2023-12-31 00:24:30 +01:00
Torsten Dittmann cc1272f80b feat: add new env variable to compsoe files 2023-12-29 22:09:59 +01:00
Torsten Dittmann 9b2fbc5c52 feat: console hostname env variable 2023-12-29 21:52:50 +01:00
Eldad A. FuxandGitHub 88a9fc380e Merge pull request #7359 from appwrite/chore-bump-console-3.3.15
Bump console to version 3.3.15
2023-12-29 20:28:32 +01:00
Steven Nguyen 3d1b61d20d Bump console to version 3.3.15 2023-12-29 19:27:44 +00:00
Eldad A. FuxandGitHub 4ffeb866ca Merge pull request #7358 from appwrite/chore-bump-console-3.3.14
Bump console to version 3.3.14
2023-12-29 20:18:53 +01:00
Steven Nguyen 643f5e845a Bump console to version 3.3.14 2023-12-29 19:16:57 +00:00
Damodar Lohani 1c236959ba fix algorithm attribute on file if size is above read buffer size 2023-12-29 01:49:56 +00:00
shimon bf0f1ee12c console version update + db lib version update 2023-12-28 16:27:34 +02:00
Damodar Lohani 9cb5eb0180 use constants for compression type 2023-12-28 11:42:40 +00:00
Damodar Lohani a28be2bf48 return when response is sent to prevent further execution 2023-12-28 11:32:04 +00:00
Damodar Lohani 5455db7c7f improve order 2023-12-28 11:26:45 +00:00
shimon 260b280e10 preview_output_fix 2023-12-28 11:54:21 +02:00
Eldad A. FuxandGitHub 86ce7a3004 Merge pull request #7349 from appwrite/pea-32-fix-preview-output
fix for file extension not supported
2023-12-28 10:21:11 +01:00
Damodar Lohani 83a009dcba update tests for preview 2023-12-28 02:22:05 +00:00
Damodar Lohani 94a18ede78 fix formatting 2023-12-28 01:35:10 +00:00
Damodar Lohani e8ff828039 fix for file extension not supported
- the error occured with jfif extension, which is essentially a jpeg file
2023-12-28 01:31:25 +00:00
Torsten DittmannandGitHub 0fdc28e034 Merge pull request #7280 from appwrite/fix-7190-user-identity-incorrect-user
Fix user identity attaching to wrong user
2023-12-28 01:59:58 +01:00
Torsten DittmannandGitHub d9b300051a Merge pull request #7292 from appwrite/fix-7147-user-last-activity-not-updating
Fix user last activity not updating
2023-12-28 01:59:23 +01:00
Steven Nguyen f1ba7b08ab Update the delete identity endpoints to set the params and payload
Because no payload was set, the event params (userId and identityId)
wasn't picked up automatically. This updates the endpoints so that the
payload is set, but also makes sure to set the userId and identityId
params since the identityId param's key doesn't match the key in the
payload.
2023-12-27 23:35:32 +00:00
shimon 3bde473ff0 update console ver 2023-12-27 16:45:33 +02:00
shimon 9889048429 update console ver 2023-12-26 19:11:13 +02:00
Shimon NewmanandGitHub 18ab5ca28f Merge pull request #7339 from appwrite/refactor-inf-metric-calc
Refactor inf metric calc
2023-12-26 18:37:08 +02:00
shimon 77d423e645 refactoring infinity metric calculation cli script, adding project id and after
options.
2023-12-26 18:23:07 +02:00
shimon 9382e4cec5 refactoring infinity metric calculation cli script, adding project id and after
options.
2023-12-26 18:21:01 +02:00
Shimon NewmanandGitHub d1f0fddbe7 Merge pull request #7330 from appwrite/refactor-usage-hook
usageHook tweaks
2023-12-25 11:42:13 +02:00
shimon 4a837907d1 usageHook tweaks 2023-12-25 11:32:40 +02:00
shimon b398338876 small fix 2023-12-25 10:17:58 +02:00
Damodar Lohani cbd3e85b38 fix formatting 2023-12-25 06:07:28 +00:00
Damodar Lohani a6b4ade39b update large file upload test to not include update permission 2023-12-25 06:06:18 +00:00
Damodar Lohani b6b1b396b3 update chunk upload test 2023-12-25 06:00:43 +00:00
Damodar Lohani 26ac88c32e validate create permission while updating chunk uploaded file 2023-12-25 05:54:59 +00:00
Shimon NewmanandGitHub a8af6fde8d Merge pull request #7326 from appwrite/refactor-usage-worker-sn
Refactor usage worker sn
2023-12-24 21:18:34 +02:00
shimon ddb7a403db refactor usage worker 2023-12-24 21:17:13 +02:00
shimon 80315a02dd refactor usage worker 2023-12-24 21:00:53 +02:00
shimon d1751565cf refactor usage worker 2023-12-24 20:38:15 +02:00
shimon f1e066d965 debuging usage 2023-12-24 17:06:12 +02:00
shimon 585b904b80 debuging usage 2023-12-24 16:50:20 +02:00
Christy JacobandGitHub e1ebf153cb Merge pull request #7320 from appwrite/update-hamster-script
chore: update hamster script
2023-12-22 15:08:04 +01:00
德华 刘 d3143b8ede chore: update hamster script 2023-12-22 14:07:05 +00:00
德华 刘 394f28146a chore: update hamster script 2023-12-22 14:03:46 +00:00
德华 刘 1857c0e04d chore: update hamster script 2023-12-22 14:03:07 +00:00
Christy JacobandGitHub 0ae5712bb6 Merge pull request #7319 from appwrite/update-console
chore: update console
2023-12-22 10:30:18 +01:00
德华 刘 1eb59daed1 chore: run linter 2023-12-22 09:23:56 +00:00
德华 刘 bf92ae6841 chore: update console 2023-12-22 09:19:27 +00:00
Christy JacobandGitHub 276a12e0f7 Merge pull request #7316 from appwrite/update-console
chore: update console
2023-12-21 23:06:25 +04:00
德华 刘 9ee949104f chore: update console 2023-12-21 19:05:18 +00:00
Christy JacobandGitHub ffa4bfd1f6 Merge pull request #7315 from appwrite/update-console
chore: update console
2023-12-21 19:57:53 +04:00
德华 刘 fac204b34e chore: update console 2023-12-21 15:57:38 +00:00
德华 刘 042c8f8f47 chore: update console 2023-12-21 15:53:29 +00:00
Christy JacobandGitHub aa9e9aa1ed Merge pull request #7314 from appwrite/update-console
Update console
2023-12-21 19:03:34 +04:00
德华 刘 6303029f8a chore: update console 2023-12-21 15:02:28 +00:00
Christy JacobandGitHub 45df2d3058 Merge pull request #7313 from appwrite/update-console
chore: update console
2023-12-21 17:14:01 +04:00
Shimon NewmanandGitHub 099308fef5 Merge pull request #7298 from appwrite/refactor-hamster-usage
combining network inbound
2023-12-21 15:05:40 +02:00
shimon ba44e06af6 metrics adjustments
after and projectId additions
2023-12-21 15:04:29 +02:00
德华 刘 a47dcac424 chore: update console 2023-12-21 12:54:35 +00:00
shimon 24d3aa499a metrics adjustments
after and projectId additions
2023-12-21 14:32:10 +02:00
shimon 4e24e3cdc2 Merge branch 'refactor-usage-sn' of github.com:appwrite/appwrite into refactor-hamster-usage
 Conflicts:
	src/Appwrite/Platform/Tasks/CalcTierStats.php
2023-12-21 14:25:06 +02:00
shimon eff578bb36 metrics adjustments
after and projectId additions
2023-12-21 14:07:24 +02:00
shimon 9a2f97b613 metrics adjustments
after and projectId additions
2023-12-21 13:45:57 +02:00
Christy JacobandGitHub 5cd94041dc Merge pull request #7312 from appwrite/1.4.x
Sync main with 1.4.x
2023-12-21 11:40:35 +04:00
Matej Bačo 9c510dace8 Update console 2023-12-20 13:30:15 +01:00
Matej Bačo 56a374e0af Upgrade console 2023-12-20 12:41:07 +01:00
Matej Bačo ccfd856e8e Merge branch 'refactor-usage-sn' of https://github.com/appwrite/appwrite into refactor-usage-sn 2023-12-20 12:31:56 +01:00
Matej Bačo 0529cffa4b Improve logging 2023-12-20 12:31:35 +01:00
Eldad A. FuxandGitHub 1023f24d0b Merge pull request #7310 from appwrite/fix-deletes-worker
fix deletes worker
2023-12-20 12:28:36 +01:00
Damodar Lohani bfbcecd069 fix deletes worker 2023-12-20 11:26:57 +00:00
Matej Bačo 05f8f8fb9d Fix SMS issue 2023-12-20 12:13:22 +01:00
Matej Bačo 457e47cdd7 Upgrade console 2023-12-20 11:12:16 +01:00
Matej Bačo 683fccc080 Update console 2023-12-19 21:28:51 +01:00
Matej Bačo 42246c5174 Fix console 2023-12-19 18:07:07 +01:00
Matej Bačo b3729507de Add cache to console collection 2023-12-19 18:05:39 +01:00
Matej Bačo b301a58b3e Update console 2023-12-19 14:51:03 +01:00
shimon 9f1b356a79 combining network inbound
and network outbound on
hamster script
2023-12-18 11:37:54 +02:00
Torsten DittmannandGitHub 76e05983ff Merge pull request #7293 from appwrite/fix-sms-import
Fix SMS import
2023-12-16 19:12:50 +01:00
Steven Nguyen e41194ee3c Fix import to match class name
The class is SMS rather than Sms.
2023-12-16 17:42:29 +00:00
Steven Nguyen 6d17f8e877 Fix user last activity not updating
Add back a section of code that was mistakenly removed.
2023-12-15 21:37:26 +00:00
Christy JacobandGitHub 0f0d43298e Merge pull request #7291 from appwrite/update-console
chore: update console
2023-12-15 21:58:28 +01:00
德华 刘 6b6946e39b chore: update console 2023-12-15 20:40:04 +00:00
Steven Nguyen 92a307c164 Fix user identity attaching to wrong user
Suppose a user has 2 accounts on Appwrite:

1. joe@example.com
2. joe@gmail.com

Prior to this PR, if joe@example.com created a Google OAuth2 session
using his joe@gmail.com email, a new joe@gmail.com identity would be
created linked to joe@example.com.

This is especially problematic because if the user tried to create a
Google OAuth2 session using joe@gmail.com, Appwrite would lookup the
user via email and find the joe@gmail.com user, but then find an
identity from joe@example.com. This mismatching user ID would then
cause an error.

This PR prevents an identity from being created if the email from the
OAuth2 provider matches another user's email.
2023-12-13 18:45:05 +00:00
Serhat AksakalandGitHub e56b30e6a6 Update tr.json 2023-12-13 12:10:44 +03:00
Christy JacobandGitHub d1850fca38 Merge pull request #7275 from appwrite/revert-expired-cookie
fix: remove expired cookie
2023-12-13 10:01:59 +01:00
德华 刘 f334168f90 fix: remove expired cookie 2023-12-13 08:45:47 +00:00
Steven Nguyen 6d357d617d Add a flag to install and upgrade commands to not start Appwrite
This can be useful for cases where the developer only wants the files
to be generated and doesn't want to start Appwrite.
2023-12-11 23:37:32 +00:00
Christy JacobandGitHub e605beaff2 Merge pull request #7260 from appwrite/fix-cookie-issue
Fix cookie issue
2023-12-09 01:54:56 +01:00
Christy Jacob 4f1d8ed6f4 fix: revert files 2023-12-09 01:36:57 +01:00
Christy Jacob 951a749b50 fix: revert files 2023-12-09 01:19:36 +01:00
Christy Jacob c21a063642 fix: failing tests 2023-12-09 01:02:13 +01:00
Christy Jacob 28d00668bd fix: failing tests 2023-12-09 00:48:34 +01:00
Christy Jacob aeeb7e1d55 fix: failing tests 2023-12-09 00:36:01 +01:00
Christy Jacob 75e1779921 fix: failing tests 2023-12-09 00:29:14 +01:00
Christy Jacob 72a2a043cd fix: linter 2023-12-09 00:28:10 +01:00
Christy Jacob aa7d1a87f4 fix: failing tests 2023-12-09 00:27:35 +01:00
Christy Jacob 731e7b86a7 fix: failing tests 2023-12-09 00:17:13 +01:00
Christy Jacob 8b5783d3ff fix: update setParam 2023-12-08 21:02:07 +01:00
Christy Jacob ab84fa8a4d Merge branch 'fix-cookie-issue' of github.com:appwrite/appwrite into fix-cookie-issue 2023-12-08 20:39:14 +01:00
Christy Jacob 9dca3c4300 fix: linter 2023-12-08 20:38:55 +01:00
Christy JacobandGitHub 5606402fa3 Merge branch '1.4.x' into fix-cookie-issue 2023-12-08 20:37:06 +01:00
Christy Jacob 5d121c1887 fix: reset the legacy cookie 2023-12-08 20:36:22 +01:00
Christy Jacob 1e9ee66231 chore: reset old cookie 2023-12-08 20:30:04 +01:00
Christy Jacob eb61d5c25c chore: reset old cookie 2023-12-08 20:24:07 +01:00
Christy Jacob f5c2c8d8f7 chore: reset old cookie 2023-12-08 20:23:04 +01:00
Jake Barnby 4710310629 Check if self-hosted 2023-12-08 18:33:35 +01:00
Torsten DittmannandGitHub 610b9518fb Merge pull request #7258 from appwrite/feat-upgrade-console-3.2.15
feat: upgrade console to 3.2.15
2023-12-08 18:21:27 +01:00
Torsten Dittmann c1af9f4201 feat: upgrade console to 3.2.15 2023-12-08 18:09:06 +01:00
Jake Barnby 4245c0b86e Update dependencies 2023-12-08 18:07:38 +01:00
Jake Barnby 6b0586eaf6 Remove DB attribute 2023-12-08 18:06:53 +01:00
Jake Barnby 018b7c38bd Merge remote-tracking branch 'origin/1.4.x' into feat-isolation-modes
# Conflicts:
#	src/Appwrite/Platform/Tasks/Hamster.php
2023-12-08 13:34:28 +01:00
Christy JacobandGitHub c60524e11f Merge pull request #7256 from appwrite/revert-7241-update-swoole-base-image
Revert "Update appwrite base image"
2023-12-07 17:50:48 +01:00
Christy JacobandGitHub ea3b9ca0ec Revert "Update appwrite base image" 2023-12-07 17:37:15 +01:00
Torsten Dittmann 14e2488f98 chore: bump version 2023-12-05 18:20:47 +01:00
Christy JacobandGitHub aae672d3be Merge pull request #7241 from appwrite/update-swoole-base-image
Update appwrite base image
2023-12-05 15:58:32 +01:00
德华 刘 298d6ea917 fix: revert qr code quality 2023-12-05 14:41:05 +00:00
德华 刘 fa7f9be6f7 fix: update image sizes in graphQL test 2023-12-05 14:25:19 +00:00
德华 刘 1595d41490 fix: update qr code test images due to Imagemagic extension upgrade in the base image 2023-12-05 14:13:24 +00:00
德华 刘 38a3c08772 chore: update docker base image 2023-12-05 09:16:35 +00:00
Christy JacobandGitHub f2d20cc5c2 Merge pull request #7240 from appwrite/update-console
chore: update console version
2023-12-05 05:12:55 +04:00
德华 刘 f127ca651f chore: update console version 2023-12-05 00:54:52 +00:00
Torsten Dittmann 4e7b61b6c8 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into 1.4.x 2023-12-04 10:45:34 +01:00
Torsten Dittmann d79411813a bump: version 2023-12-04 10:45:18 +01:00
Torsten DittmannandGitHub 4d0242b48b Merge pull request #7238 from appwrite/bump-console
bump: console version
2023-12-04 10:26:05 +01:00
Torsten Dittmann eb87b951c6 chore: bump console version 2023-12-04 10:23:29 +01:00
Christy JacobandGitHub fa6f9acaed Merge pull request #7216 from appwrite/feat-refactor-hamster
Implement Job based hamster
2023-12-01 01:36:33 +04:00
德华 刘 65b1e1e948 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into feat-refactor-hamster 2023-11-30 21:26:49 +00:00
Christy JacobandGitHub 62808bfb8b Merge pull request #7225 from appwrite/update-console
feat: update console
2023-12-01 01:26:15 +04:00
德华 刘 8218e95f17 chore: revert console 2023-11-30 21:26:07 +00:00
德华 刘 b6fabf80ef Merge branch '1.4.x' of https://github.com/appwrite/appwrite into feat-refactor-hamster 2023-11-30 21:20:59 +00:00
德华 刘 11a9583c72 feat: update cache buster 2023-11-30 21:16:17 +00:00
德华 刘 49bb3444bf chore: add logs 2023-11-30 21:11:31 +00:00
德华 刘 e382a9d58b feat: update console 2023-11-30 20:37:59 +00:00
德华 刘 d4ff696173 chore: fix linter issues 2023-11-30 20:35:18 +00:00
德华 刘 0382b2250a chore: address review comments 2023-11-30 19:22:35 +00:00
德华 刘 799fe2acca chore: address review comments 2023-11-30 17:43:07 +00:00
德华 刘 f3544485e5 chore: address review comments 2023-11-30 17:41:23 +00:00
德华 刘 4b7676158e chore: address review comments 2023-11-30 16:24:11 +00:00
德华 刘 5cb818066f Merge branch 'feat-refactor-hamster' of https://github.com/appwrite/appwrite into feat-refactor-hamster 2023-11-30 16:23:05 +00:00
德华 刘 d34050a5df chore: address review comments 2023-11-30 16:22:26 +00:00
Bradley Schofield 8bec64b2a2 Update Hamster.php 2023-11-30 11:05:15 +00:00
Jake Barnby 7e12da5a2a Use hard-coded DB name instead of project attribute 2023-11-30 16:32:15 +13:00
Jake Barnby e109f1e008 Add metadata in worker dbs 2023-11-30 16:06:49 +13:00
Jake Barnby 4f539f1ed4 Update switch to match 2023-11-30 16:04:15 +13:00
Bradley Schofield dea3e74b6a Implement Job based hamster 2023-11-28 10:19:55 +00:00
Jake Barnby 7104f9e2c8 Add comments marking shareTables project attributes + header overrides as temporary 2023-11-28 02:08:04 +13:00
Jake Barnby 632c902e1c Remove debug trace 2023-11-28 01:53:24 +13:00
Jake Barnby c1f10b06a3 Fix index limit test 2023-11-28 01:34:29 +13:00
Jake Barnby c4806e883e Update lock 2023-11-28 01:00:22 +13:00
Jake Barnby 2c71de1d1b Merge remote-tracking branch 'origin/1.4.x' into feat-isolation-modes
# Conflicts:
#	composer.lock
2023-11-28 00:57:17 +13:00
Jake Barnby 2e89113e6a Self review 2023-11-28 00:52:46 +13:00
Jake Barnby edee5400ba Fix casing 2023-11-28 00:43:21 +13:00
Jake Barnby 82dfe5ddee Add tests 2023-11-28 00:30:02 +13:00
Jake Barnby 0885f31dd7 Update database 2023-11-28 00:29:23 +13:00
Jake Barnby 9472bff8f9 Fix project create tenant set/reset 2023-11-28 00:27:41 +13:00
Jake Barnby 68548ebd47 Fix worker tenant set/reset 2023-11-28 00:27:16 +13:00
Jake Barnby b9ac8e644d Set/reset tenant on init 2023-11-28 00:26:35 +13:00
Jake Barnby 7b8e37b03d Update method names 2023-11-27 15:22:05 +13:00
shimon 9960912bb1 sync against main 2023-11-26 18:52:02 +02:00
shimon 1869852985 Merge branch 'main' of github.com:appwrite/appwrite into refactor-cache-poc
 Conflicts:
	app/controllers/shared/api.php
	composer.lock
	src/Appwrite/Platform/Workers/Deletes.php
2023-11-26 16:29:18 +02:00
Jake Barnby 35163ff52a Check project shareTables status when configuring databases 2023-11-23 01:13:24 +13:00
Jake Barnby f44543f083 Add shareTables attribute to project 2023-11-23 01:12:19 +13:00
Jake Barnby cefc1b6519 Add base share tables check when creating project 2023-11-23 01:11:48 +13:00
Jake Barnby eb5fc0797f Update abuse/audit 2023-11-23 00:43:32 +13:00
Jake Barnby 464b913e90 Update for function name change 2023-11-22 21:32:36 +13:00
shimon 336afc5ca2 Merge branch 'main' of github.com:appwrite/appwrite into remove-deletes-db-leftovers 2023-11-19 15:30:36 +02:00
loks0n 33f92d9ab3 chore: fmt 2023-11-17 18:12:15 +00:00
loks0n 61e2b7a787 feat: more phone validation 2023-11-17 18:09:20 +00:00
shimon 5a3fd99a28 remove deleteCollection()
and deleteDatabase() from deletes worker
2023-11-14 20:05:59 +02:00
Pratik GuptaandGitHub eefbc68f6b Update web GETTING_STARTED.md
Removed phone number from account.create() function as it does not accept it.
2023-10-24 09:12:13 +05:30
Pratik GuptaandGitHub 7791cdc201 Rectified flutter EXAMPLES.md
Removed phone number from account.create() function as it does not accept it.
2023-10-24 09:09:18 +05:30
Pratik GuptaandGitHub daec743e8c Rectified flutter GETTING_STARTED.md
Removed phone number from account.create() function as it does not accept it.
2023-10-24 09:07:04 +05:30
Pratik GuptaandGitHub c2ce7d0aa1 Rectified flutter-dev EXAMPLES.md
Removed phone number from account.create() function as it does not accept it.
2023-10-24 09:04:48 +05:30
Pratik GuptaandGitHub 9eea574df5 Rectified apple GETTING_STARTED.md
Removed phone number from account.create() function as it does not accept it.
2023-10-24 09:01:28 +05:30
Pratik GuptaandGitHub da504ba46c Rectified android GETTING_STARTED.md
removed phone number from account.create() function as it does not accept it.
2023-10-24 08:59:36 +05:30
Pratik GuptaandGitHub c2c645188d Update nodejs GETTING_STARTED.md 2023-10-19 21:10:08 +05:30
Pratik GuptaandGitHub 7d23b0af5f Update dotnet GETTING_STARTED.md 2023-10-19 21:08:47 +05:30
Pratik GuptaandGitHub 0853de0760 Update web GETTING_STARTED.md 2023-10-19 01:21:22 +05:30
Pratik GuptaandGitHub b2e4513af3 Update swift GETTING_STARTED.md 2023-10-19 01:19:07 +05:30
Pratik GuptaandGitHub a26c9214cb Update ruby GETTING_STARTED.md 2023-10-19 01:17:18 +05:30
Pratik GuptaandGitHub d3b91b5ec5 Update python GETTING_STARTED.md 2023-10-19 01:14:58 +05:30
Pratik GuptaandGitHub a5ea32c237 Update php GETTING_STARTED.md 2023-10-19 01:12:31 +05:30
Pratik GuptaandGitHub 88efb36ead Update nodejs GETTING_STARTED.md 2023-10-19 01:10:28 +05:30
Pratik GuptaandGitHub 902f20f20c Update kotlin GETTING_STARTED.md 2023-10-19 01:08:06 +05:30
Pratik GuptaandGitHub f6281158a2 Update flutter EXAMPLES.md 2023-10-19 01:05:43 +05:30
Pratik GuptaandGitHub 154e972217 Update flutter GETTING_STARTED.md 2023-10-19 01:04:32 +05:30
Pratik GuptaandGitHub 0c2374e66a Update flutter-dev EXAMPLES.md 2023-10-19 01:02:17 +05:30
Pratik GuptaandGitHub 15863c2886 Update dotnet GETTING_STARTED.md 2023-10-19 00:57:12 +05:30
Pratik GuptaandGitHub 618c14524d Update deno GETTING_STARTED.md 2023-10-19 00:55:24 +05:30
Pratik GuptaandGitHub 6c4983f65f Update dart EXAMPLES.md 2023-10-19 00:52:39 +05:30
Pratik GuptaandGitHub ea282a1761 Update dart GETTING_STARTED.md 2023-10-19 00:51:10 +05:30
Pratik GuptaandGitHub 8fed930b7d Update apple GETTING_STARTED.md 2023-10-19 00:43:47 +05:30
Pratik GuptaandGitHub a66fc13ebc Update android GETTING_STARTED.md 2023-10-19 00:40:09 +05:30
Pratik GuptaandGitHub 8d7ab130ff changes made web GETTING_STARTED.md 2023-10-17 18:52:28 +05:30
Pratik GuptaandGitHub 569d54bee3 changes made swift GETTING_STARTED.md 2023-10-17 18:49:51 +05:30
Pratik GuptaandGitHub 5138d4c0f9 changes made ruby GETTING_STARTED.md 2023-10-17 18:45:24 +05:30
Pratik GuptaandGitHub d37bfa0a5e changes made php GETTING_STARTED.md 2023-10-17 18:41:10 +05:30
Pratik GuptaandGitHub 2cce14418c changes made nodejs GETTING_STARTED.md 2023-10-17 18:37:53 +05:30
Pratik GuptaandGitHub e6b0d22e07 changes made flutter EXAMPLES.md 2023-10-17 18:26:57 +05:30
Pratik GuptaandGitHub ba8cafbd45 changes made flutter GETTING_STARTED.md 2023-10-17 18:25:00 +05:30
Pratik GuptaandGitHub e920c1f8b4 changes made flutter-dev EXAMPLES.md 2023-10-17 18:21:19 +05:30
Pratik GuptaandGitHub 8ac7aca2a4 changes made dotnet GETTING_STARTED.md 2023-10-17 18:19:02 +05:30
Pratik GuptaandGitHub ec17284bfd changes made deno GETTING_STARTED.md 2023-10-17 18:15:42 +05:30
Pratik GuptaandGitHub 2d123f60e1 changes made dart EXAMPLES.md 2023-10-17 18:12:29 +05:30
Pratik GuptaandGitHub 7721f29d0e changes made dart GETTING_STARTED.md 2023-10-17 18:09:33 +05:30
Pratik GuptaandGitHub af87c24cfd changes made apple GETTING_STARTED.md 2023-10-17 18:06:43 +05:30
Pratik GuptaandGitHub 4b7c13d3e2 changes made android GETTING_STARTED.md 2023-10-17 18:02:37 +05:30
Pratik GuptaandGitHub bb9dd4423d Update web GETTING_STARTED.md 2023-10-17 01:49:47 +05:30
Pratik GuptaandGitHub 303212fba8 Update swift GETTING_STARTED.md 2023-10-17 01:47:43 +05:30
Pratik GuptaandGitHub acc408f5ab Update ruby GETTING_STARTED.md 2023-10-17 01:45:54 +05:30
Pratik GuptaandGitHub 086116d0f0 Update php GETTING_STARTED.md 2023-10-17 01:43:09 +05:30
Pratik GuptaandGitHub e93681afc0 Update nodejs GETTING_STARTED.md 2023-10-17 01:36:49 +05:30
Pratik GuptaandGitHub 7d2b4503e3 Update kotlin GETTING_STARTED.md 2023-10-17 01:34:32 +05:30
Pratik GuptaandGitHub 0d314df561 Update flutter EXAMPLES.md 2023-10-17 01:31:20 +05:30
Pratik GuptaandGitHub 006d4237f5 Update flutter GETTING_STARTED.md 2023-10-17 01:30:16 +05:30
Pratik GuptaandGitHub 2c3b860e63 Update flutter-dev EXAMPLES.md 2023-10-17 01:28:09 +05:30
Pratik GuptaandGitHub 613a733169 Update dart EXAMPLES.md 2023-10-17 01:25:54 +05:30
Pratik GuptaandGitHub 0587aec643 Update dotnet GETTING_STARTED.md 2023-10-17 01:22:16 +05:30
Pratik GuptaandGitHub b334f2e345 Update deno GETTING_STARTED.md 2023-10-17 01:19:54 +05:30
Pratik GuptaandGitHub 777c8dd587 Update dart GETTING_STARTED.md 2023-10-17 01:18:01 +05:30
Pratik GuptaandGitHub 4be8be1353 Update apple GETTING_STARTED.md 2023-10-17 01:14:26 +05:30
Pratik GuptaandGitHub 8cc1c0d6dc Update android GETTING_STARTED.md 2023-10-17 01:07:53 +05:30
Vincent (Wen Yu) GeandGitHub eaba2eb7e5 Merge branch 'main' into doc-sdks-python-update-getting-started-guide 2023-10-12 11:00:06 -04:00
Pratik GuptaandGitHub 9ccaa5a883 Update GETTING_STARTED.md
Changed user1 name and (email1 -> email).
2023-10-11 18:14:12 +05:30
Pratik GuptaandGitHub 93fa7496b1 Update GETTING_STARTED.md
Fixed the issue mentioned in sdk-for-python repository Issue - 62.
Issue link - https://github.com/appwrite/sdk-for-python/issues/62
2023-10-06 18:10:24 +05:30
shimon 45f16e560c sync against master 2023-09-05 21:17:54 +03:00
shimon ec93239630 Merge branch 'master' of github.com:appwrite/appwrite into refactor-cache-poc
 Conflicts:
	app/config/collections.php
	app/controllers/api/storage.php
	app/controllers/shared/api.php
	app/init.php
	app/workers/deletes.php
2023-09-05 19:19:59 +03:00
shimon 2f7b0c9938 added bucketId to cache::deleteByResource 2023-07-22 17:31:33 +03:00
shimon ef12b75013 Merge branch 'master' of github.com:appwrite/appwrite into refactor-cache-poc
 Conflicts:
	app/init.php
2023-07-22 17:09:49 +03:00
shimon bed88baa6c added bucketId to cache::deleteByResource 2023-07-22 17:08:28 +03:00
shimon 9562b95285 saving file output to cache instead of decoding data to json 2023-05-31 19:34:12 +03:00
shimon e9fda6168c saving file output to cache instead of decoding data to json 2023-05-31 15:50:19 +03:00
shimon a6768bdc32 saving file output to cache instead of decoding data to json 2023-05-31 14:41:56 +03:00
309 changed files with 4891 additions and 2417 deletions
+6 -2
View File
@@ -1,15 +1,17 @@
_APP_ENV=development
_APP_EDITION=self-hosted
_APP_LOCALE=en
_APP_WORKER_PER_CORE=6
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_WHITELIST_IPS=
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_ROUTER_PROTECTION=disbled
_APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
@@ -57,6 +59,7 @@ _APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMS_PROVIDER=sms://username:password@mock
_APP_SMS_FROM=+123456789
_APP_SMS_PROJECTS_DENY_LIST=
_APP_STORAGE_LIMIT=30000000
_APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_FUNCTIONS_SIZE_LIMIT=30000000
@@ -71,11 +74,12 @@ _APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://proxy/v1
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_DELAY=
_APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_USAGE_AGGREGATION_INTERVAL=5
_APP_USAGE_AGGREGATION_INTERVAL=20
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_USAGE_STATS=enabled
+8 -2
View File
@@ -17,6 +17,12 @@ jobs:
fetch-depth: 2
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
@@ -35,11 +41,11 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
build-args: |
VERSION=${{ steps.meta.outputs.version }}
VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
VITE_GA_PROJECT=G-L7G2B6PLDS
VITE_CONSOLE_MODE=cloud
push: true
tags: ${{ steps.meta.outputs.tags }}
tags: ${{ steps.meta.outputs.tags }}
+27 -3
View File
@@ -75,8 +75,32 @@ jobs:
- name: Run Unit Tests
run: docker compose exec appwrite test /usr/src/code/tests/unit
e2e_test:
name: E2E Test
e2e_general_test:
name: E2E General Test
runs-on: ubuntu-latest
needs: setup
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 General Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug
e2e_service_test:
name: E2E Service Test
runs-on: ubuntu-latest
needs: setup
strategy:
@@ -119,4 +143,4 @@ jobs:
sleep 10
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
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
appwrite.json
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = billing
branch = 3.2.16
+8 -1
View File
@@ -84,6 +84,10 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/ssl && \
chmod +x /usr/local/bin/test && \
chmod +x /usr/local/bin/vars && \
chmod +x /usr/local/bin/queue-retry && \
chmod +x /usr/local/bin/queue-count-failed && \
chmod +x /usr/local/bin/queue-count-processing && \
chmod +x /usr/local/bin/queue-count-success && \
chmod +x /usr/local/bin/worker-audits && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-databases && \
@@ -93,8 +97,11 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/worker-migrations && \
chmod +x /usr/local/bin/worker-hamster && \
chmod +x /usr/local/bin/worker-usage && \
chmod +x /usr/local/bin/worker-migrations
chmod +x /usr/local/bin/worker-usage-dump
# Cloud Executabless
RUN chmod +x /usr/local/bin/hamster && \
+31 -3
View File
@@ -6,6 +6,7 @@ require_once __DIR__ . '/controllers/general.php';
use Appwrite\Event\Delete;
use Appwrite\Event\Certificate;
use Appwrite\Event\Func;
use Appwrite\Event\Hamster;
use Appwrite\Platform\Appwrite;
use Utopia\CLI\CLI;
use Utopia\Database\Validator\Authorization;
@@ -24,7 +25,7 @@ use Utopia\Registry\Registry;
Authorization::disable();
CLI::setResource('register', fn()=>$register);
CLI::setResource('register', fn() => $register);
CLI::setResource('cache', function ($pools) {
$list = Config::getParam('pools-cache', []);
@@ -71,7 +72,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
$collections = Config::getParam('collections', [])['console'];
$last = \array_key_last($collections);
if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) { /** TODO cache ready variable using registry */
if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */
throw new Exception('Tables not ready yet.');
}
@@ -102,7 +103,19 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
if (isset($databases[$databaseName])) {
$database = $databases[$databaseName];
$database->setNamespace('_' . $project->getInternalId());
if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$database
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
return $database;
}
@@ -115,6 +128,18 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
$databases[$databaseName] = $database;
if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$database
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
@@ -130,6 +155,9 @@ CLI::setResource('queue', function (Group $pools) {
CLI::setResource('queueForFunctions', function (Connection $queue) {
return new Func($queue);
}, ['queue']);
CLI::setResource('queueForHamster', function (Connection $queue) {
return new Hamster($queue);
}, ['queue']);
CLI::setResource('queueForDeletes', function (Connection $queue) {
return new Delete($queue);
}, ['queue']);
+57 -57
View File
@@ -18,6 +18,63 @@ $auth = Config::getParam('auth', []);
*/
$commonCollections = [
'cache' => [
'$collection' => Database::METADATA,
'$id' => 'cache',
'name' => 'Cache',
'attributes' => [
[
'$id' => 'resource',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'accessedAt',
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => 'signature',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_accessedAt',
'type' => Database::INDEX_KEY,
'attributes' => ['accessedAt'],
'lengths' => [],
'orders' => [],
],
[
'$id' => '_key_resource',
'type' => Database::INDEX_KEY,
'attributes' => ['resource'],
'lengths' => [],
'orders' => [],
],
],
],
'users' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('users'),
@@ -2872,63 +2929,6 @@ $projectCollections = array_merge([
],
],
'cache' => [
'$collection' => Database::METADATA,
'$id' => 'cache',
'name' => 'Cache',
'attributes' => [
[
'$id' => 'resource',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'accessedAt',
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => 'signature',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_accessedAt',
'type' => Database::INDEX_KEY,
'attributes' => ['accessedAt'],
'lengths' => [],
'orders' => [],
],
[
'$id' => '_key_resource',
'type' => Database::INDEX_KEY,
'attributes' => ['resource'],
'lengths' => [],
'orders' => [],
],
],
],
'variables' => [
'$collection' => Database::METADATA,
'$id' => 'variables',
+26 -3
View File
@@ -31,7 +31,7 @@ return [
Exception::GENERAL_SERVICE_DISABLED => [
'name' => Exception::GENERAL_SERVICE_DISABLED,
'description' => 'The requested service is disabled. You can enable the service from the Appwrite console.',
'code' => 503,
'code' => 403,
],
Exception::GENERAL_UNAUTHORIZED_SCOPE => [
'name' => Exception::GENERAL_UNAUTHORIZED_SCOPE,
@@ -103,6 +103,11 @@ 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_BAD_REQUEST => [
'name' => Exception::GENERAL_BAD_REQUEST,
'description' => 'There was an error processing your request. Please check the inputs and try again.',
'code' => 400,
],
/** User Errors */
Exception::USER_COUNT_EXCEEDED => [
@@ -251,6 +256,11 @@ return [
'description' => 'User phone is already verified',
'code' => 409
],
Exception::USER_DELETION_PROHIBITED => [
'name' => Exception::USER_DELETION_PROHIBITED,
'description' => 'User deletion is not allowed for users with active memberships. Please delete all confirmed memberships before deleting the account.',
'code' => 400
],
/** Teams */
Exception::TEAM_NOT_FOUND => [
@@ -680,6 +690,7 @@ return [
'name' => Exception::RULE_VERIFICATION_FAILED,
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
'code' => 401,
'publish' => true
],
Exception::PROJECT_SMTP_CONFIG_INVALID => [
'name' => Exception::PROJECT_SMTP_CONFIG_INVALID,
@@ -767,10 +778,22 @@ return [
],
/** Health */
Exception::QUEUE_SIZE_EXCEEDED => [
'name' => Exception::QUEUE_SIZE_EXCEEDED,
Exception::HEALTH_QUEUE_SIZE_EXCEEDED => [
'name' => Exception::HEALTH_QUEUE_SIZE_EXCEEDED,
'description' => 'Queue size threshold hit.',
'code' => 503,
'publish' => false
],
Exception::HEALTH_CERTIFICATE_EXPIRED => [
'name' => Exception::HEALTH_CERTIFICATE_EXPIRED,
'description' => 'The SSL certificate for the specified domain has expired and is no longer valid.',
'code' => 404,
],
Exception::HEALTH_INVALID_HOST => [
'name' => Exception::HEALTH_INVALID_HOST,
'description' => 'Failed to establish a connection to the specified domain. Please verify the domain name and ensure that the server is running and accessible.',
'code' => 404,
],
];
+31 -25
View File
@@ -3,30 +3,36 @@
"settings.locale": "tr",
"settings.direction": "ltr",
"emails.sender": "%s Takımı",
"emails.verification.subject": "",
"emails.verification.hello": "",
"emails.verification.body": "",
"emails.verification.footer": "",
"emails.verification.thanks": "",
"emails.verification.signature": "",
"emails.magicSession.subject": "",
"emails.magicSession.hello": "",
"emails.magicSession.body": "",
"emails.magicSession.footer": "",
"emails.magicSession.thanks": "",
"emails.magicSession.signature": "",
"emails.recovery.subject": "",
"emails.recovery.hello": "",
"emails.recovery.body": "",
"emails.recovery.footer": "",
"emails.recovery.thanks": "",
"emails.recovery.signature": "",
"emails.invitation.subject": "",
"emails.invitation.hello": "",
"emails.invitation.body": "",
"emails.invitation.footer": "",
"emails.invitation.thanks": "",
"emails.invitation.signature": "",
"emails.verification.subject": "Hesabını Doğrula",
"emails.verification.hello": "Merhaba {{user}}",
"emails.verification.body": "Eposta adresini doğrulamak için bu bağlantıyı kullanın.",
"emails.verification.footer": "Eğer bu eposta adresini doğrulamak isteyen siz değilseniz devam etmeyin.",
"emails.verification.thanks": "Teşekkürler",
"emails.verification.signature": "{{project}} takımı",
"emails.magicSession.subject": "Giriş",
"emails.magicSession.hello": "Merhaba,",
"emails.magicSession.body": "Giriş yapmak için tıklayın.",
"emails.magicSession.footer": "Eğer bu eposta adresini kullanarak giriş yapmak istemediyseniz devam etmeyin.",
"emails.magicSession.thanks": "Teşekkürler",
"emails.magicSession.signature": "{{project}} takımı",
"emails.recovery.subject": "Şifremi Sıfırla",
"emails.recovery.hello": "Merhaba {{user}}",
"emails.recovery.body": "{{project}} şifrenizi sıfırlamak için bu bağlantıyı kullanın.",
"emails.recovery.footer": "Eğer şifre sıfırlama talebinde bulunmadıysanız devam etmeyin.",
"emails.recovery.thanks": "Teşekkürler",
"emails.recovery.signature": "{{project}} takımı",
"emails.invitation.subject": "%s üzerinde %s Takımına Davet",
"emails.invitation.hello": "Merhaba",
"emails.invitation.body": "Bu epostayı aldınız, çünkü {{owner}} sizi {{project}} üzerinde {{team}} takımının üyesi olmaya davet etti.",
"emails.invitation.footer": "Eğer ilgilenmiyorsanız devam etmeyin.",
"emails.invitation.thanks": "Teşekkürler",
"emails.invitation.signature": "{{project}} takımı",
"emails.certificate.subject": "%s için sertifika hatası",
"emails.certificate.hello": "Merhaba",
"emails.certificate.body": "Alan adınız '{{domain}}' için sertifika oluşturulamadı. Deneme sayısı {{attempt}} ve hata sebebi: {{error}}",
"emails.certificate.footer": "Geçmiş sertifikanız ilk denemeden sonra 30 gün daha geçerli kalacaktır. Bu konuyu araştırmanızı öneriyoruz, aksi taktirde alan adınız SSL sertifikasız kalacaktır.",
"emails.certificate.thanks": "Teşekkürler",
"emails.certificate.signature": "{{project}} takımı",
"locale.country.unknown": "Bilinmeyen",
"countries.af": "Afganistan",
"countries.ao": "Angola",
@@ -229,4 +235,4 @@
"continents.na": "Kuzey Amerika",
"continents.oc": "Okyanusya",
"continents.sa": "Güney Amerika"
}
}
+3 -3
View File
@@ -185,7 +185,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.4.2',
'version' => '0.5.0',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@@ -196,7 +196,7 @@ return [
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
'gitUrl' => 'https://github.com/appwrite/sdk-for-console.git',
'gitBranch' => 'dev',
'gitBranch' => 'main',
'gitRepoName' => 'sdk-for-console',
'gitUserName' => 'appwrite',
],
@@ -303,7 +303,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '10.1.0',
'version' => '10.1.1',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
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
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
+19 -9
View File
@@ -115,14 +115,6 @@ return [
'question' => '',
'filter' => ''
],
// [
// 'name' => '_APP_CONSOLE_WHITELIST_DOMAINS',
// 'description' => 'This option allows you to limit creation of users to Appwrite console for users sharing the same email domains. This option is very useful for team working with company emails domain.\n\nTo enable this option, pass a list of allowed email domains separated by a comma.',
// 'introduction' => '',
// 'default' => '',
// 'required' => false,
// 'question' => '',
// ],
[
'name' => '_APP_CONSOLE_WHITELIST_IPS',
'description' => "This last option allows you to limit creation of users in Appwrite console for users sharing the same set of IP addresses. This option is very useful for team working with a VPN service or a company IP.\n\nTo enable/activate this option, pass a list of allowed IP addresses separated by a comma.",
@@ -132,6 +124,15 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_CONSOLE_HOSTNAMES',
'description' => 'This option allows you to add additional hostnames to your Appwrite console. This option is very useful for allowing access to the console project from additional domains. To enable it, pass a list of allowed hostnames separated by a comma.',
'introduction' => '1.5.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_SYSTEM_EMAIL_NAME',
'description' => 'This is the sender name value that will appear on email messages sent to developers from the Appwrite console. The default value is: \'Appwrite\'. You can use url encoded strings for spaces and special chars.',
@@ -407,7 +408,7 @@ return [
],
[
'name' => '_APP_SMTP_SECURE',
'description' => 'SMTP secure connection protocol. Empty by default, change to \'tls\' if running on a secure connection.',
'description' => 'SMTP secure connection protocol. Empty by default, change to \'tls\' or \'ssl\' if running on a secure connection.',
'introduction' => '',
'default' => '',
'required' => false,
@@ -947,6 +948,15 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_MAINTENANCE_DELAY',
'description' => 'Delay value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 0 seconds.',
'introduction' => '1.5.0',
'default' => '0',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_MAINTENANCE_RETENTION_CACHE',
'description' => 'The maximum duration (in seconds) upto which to retain cached files. The default value is 2592000 seconds (30 days).',
+140 -56
View File
@@ -45,6 +45,7 @@ use Utopia\Validator\WhiteList;
use Appwrite\Auth\Validator\PasswordHistory;
use Appwrite\Auth\Validator\PasswordDictionary;
use Appwrite\Auth\Validator\PersonalData;
use Appwrite\Event\Delete;
use Appwrite\Hooks\Hooks;
$oauthDefaultSuccess = '/auth/oauth2/success';
@@ -109,7 +110,7 @@ App::post('/v1/account')
Query::equal('providerEmail', [$email]),
]);
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
if ($project->getAttribute('auths', [])['personalDataCheck'] ?? false) {
@@ -119,7 +120,7 @@ App::post('/v1/account')
}
}
$hooks->trigger('passwordValidator', [$project, $password, $user]);
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
@@ -196,7 +197,8 @@ App::post('/v1/account/sessions/email')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
->inject('hooks')
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Hooks $hooks) {
$email = \strtolower($email);
$protocol = $request->getProtocol();
@@ -215,6 +217,8 @@ App::post('/v1/account/sessions/email')
$user->setAttributes($profile->getArrayCopy());
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$detector = new Detector($request->getUserAgent('UNKNOWN'));
@@ -249,7 +253,7 @@ App::post('/v1/account/sessions/email')
$dbForProject->updateDocument('users', $user->getId(), $user);
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
Permission::read(Role::user($user->getId())),
@@ -549,11 +553,19 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
if (!$user->isEmpty()) {
$userId = $user->getId();
$identitiesWithMatchingEmail = $dbForProject->find('identities', [
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
Query::equal('providerEmail', [$email]),
Query::notEqual('userId', $userId),
]);
if (!empty($identitiesWithMatchingEmail)) {
if (!empty($identityWithMatchingEmail)) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
$userWithMatchingEmail = $dbForProject->find('users', [
Query::equal('email', [$email]),
Query::notEqual('$id', $userId),
]);
if (!empty($userWithMatchingEmail)) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
}
@@ -566,7 +578,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$currentDocument = $dbForProject->getDocument('sessions', $current);
if (!$currentDocument->isEmpty()) {
$dbForProject->deleteDocument('sessions', $currentDocument->getId());
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
}
}
@@ -625,7 +637,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
Query::equal('providerEmail', [$email]),
]);
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
try {
@@ -683,7 +695,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
Query::notEqual('userId', $user->getId()),
]);
if (!empty($identitiesWithMatchingEmail)) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
$dbForProject->createDocument('identities', new Document([
@@ -754,7 +766,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$session->setAttribute('expire', $expire);
@@ -840,7 +852,7 @@ App::get('/v1/account/identities')
});
App::delete('/v1/account/identities/:identityId')
->desc('Delete Identity')
->desc('Delete identity')
->groups(['api', 'account'])
->label('scope', 'account')
->label('event', 'users.[userId].identities.[identityId].delete')
@@ -856,7 +868,8 @@ App::delete('/v1/account/identities/:identityId')
->param('identityId', '', new UID(), 'Identity ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $identityId, Response $response, Database $dbForProject) {
->inject('queueForEvents')
->action(function (string $identityId, Response $response, Database $dbForProject, Event $queueForEvents) {
$identity = $dbForProject->getDocument('identities', $identityId);
@@ -866,12 +879,17 @@ App::delete('/v1/account/identities/:identityId')
$dbForProject->deleteDocument('identities', $identityId);
$queueForEvents
->setParam('userId', $identity->getAttribute('userId'))
->setParam('identityId', $identity->getId())
->setPayload($response->output($identity, Response::MODEL_IDENTITY));
return $response->noContent();
});
App::post('/v1/account/sessions/magic-url')
->desc('Create magic URL session')
->groups(['api', 'account'])
->groups(['api', 'account', 'auth'])
->label('scope', 'public')
->label('auth.type', 'magic-url')
->label('audits.event', 'session.create')
@@ -884,8 +902,8 @@ App::post('/v1/account/sessions/magic-url')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->label('abuse-limit', 60)
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
@@ -926,7 +944,7 @@ App::post('/v1/account/sessions/magic-url')
Query::equal('providerEmail', [$email]),
]);
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
$userId = $userId === 'unique()' ? ID::unique() : $userId;
@@ -982,7 +1000,7 @@ App::post('/v1/account/sessions/magic-url')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
if (empty($url)) {
$url = $request->getProtocol() . '://' . $request->getHostname() . '/auth/magic-url';
@@ -998,7 +1016,7 @@ App::post('/v1/account/sessions/magic-url')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{body}}', $body)
->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.magicSession.hello"))
->setParam('{{footer}}', $locale->getText("emails.magicSession.footer"))
->setParam('{{thanks}}', $locale->getText("emails.magicSession.thanks"))
@@ -1053,11 +1071,11 @@ App::post('/v1/account/sessions/magic-url')
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */
'user' => '',
'team' => '',
'project' => $project->getAttribute('name'),
'redirect' => $url
'redirect' => $url,
'project' => $project->getAttribute('name')
];
$queueForMails
@@ -1159,7 +1177,7 @@ App::put('/v1/account/sessions/magic-url')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$tokens = $user->getAttribute('tokens', []);
@@ -1168,7 +1186,7 @@ App::put('/v1/account/sessions/magic-url')
* the recovery token but actually we don't need it anymore.
*/
$dbForProject->deleteDocument('tokens', $token);
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$user->setAttribute('emailVerification', true);
@@ -1205,7 +1223,7 @@ App::put('/v1/account/sessions/magic-url')
App::post('/v1/account/sessions/phone')
->desc('Create phone session')
->groups(['api', 'account'])
->groups(['api', 'account', 'auth'])
->label('scope', 'public')
->label('auth.type', 'phone')
->label('audits.event', 'session.create')
@@ -1219,7 +1237,7 @@ App::post('/v1/account/sessions/phone')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},phone:{param-phone}')
->label('abuse-key', ['url:{url},phone:{param-phone}', 'url:{url},ip:{ip}'])
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->inject('request')
@@ -1306,7 +1324,7 @@ App::post('/v1/account/sessions/phone')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
@@ -1321,6 +1339,7 @@ App::post('/v1/account/sessions/phone')
$queueForMessaging
->setRecipient($phone)
->setMessage($message)
->setProject($project)
->trigger();
$queueForEvents->setPayload(
@@ -1410,14 +1429,14 @@ App::put('/v1/account/sessions/phone')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
/**
* We act like we're updating and validating
* the recovery token but actually we don't need it anymore.
*/
$dbForProject->deleteDocument('tokens', $token);
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$user->setAttribute('phoneVerification', true);
@@ -1562,7 +1581,7 @@ App::post('/v1/account/sessions/anonymous')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -1879,7 +1898,8 @@ App::patch('/v1/account/password')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
->inject('hooks')
->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
// Check old password only if its an existing user.
if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
@@ -1906,6 +1926,8 @@ App::patch('/v1/account/password')
}
}
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
$user
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
@@ -1943,7 +1965,9 @@ App::patch('/v1/account/email')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
->inject('project')
->inject('hooks')
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) {
// passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -1954,6 +1978,8 @@ App::patch('/v1/account/email')
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
$email = \strtolower($email);
// Makes sure this email is not already used in another identity
@@ -1962,7 +1988,7 @@ App::patch('/v1/account/email')
Query::notEqual('userId', $user->getId()),
]);
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
$user
@@ -1981,7 +2007,7 @@ App::patch('/v1/account/email')
try {
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
} catch (Duplicate) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
$queueForEvents->setParam('userId', $user->getId());
@@ -2012,7 +2038,9 @@ App::patch('/v1/account/phone')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
->inject('project')
->inject('hooks')
->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) {
// passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -2023,6 +2051,8 @@ App::patch('/v1/account/phone')
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
$user
->setAttribute('phone', $phone)
->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again
@@ -2184,7 +2214,7 @@ App::delete('/v1/account/sessions/:sessionId')
;
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -2264,7 +2294,7 @@ App::patch('/v1/account/sessions/:sessionId')
$dbForProject->updateDocument('sessions', $sessionId, $session);
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
@@ -2334,7 +2364,7 @@ App::delete('/v1/account/sessions')
}
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -2359,7 +2389,7 @@ App::post('/v1/account/recovery')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', ['url:{url},email:{param-email}', 'ip:{ip}'])
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
->param('email', '', new Email(), 'User email.')
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients'])
->inject('request')
@@ -2419,7 +2449,7 @@ App::post('/v1/account/recovery')
Permission::delete(Role::user($profile->getId())),
]));
$dbForProject->deleteCachedDocument('users', $profile->getId());
$dbForProject->purgeCachedDocument('users', $profile->getId());
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
@@ -2432,7 +2462,7 @@ App::post('/v1/account/recovery')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{body}}', $body)
->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
->setParam('{{footer}}', $locale->getText("emails.recovery.footer"))
->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks"))
@@ -2487,16 +2517,16 @@ App::post('/v1/account/recovery')
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */
'user' => $profile->getAttribute('name'),
'team' => '',
'project' => $projectName,
'redirect' => $url
'redirect' => $url,
'project' => $projectName
];
$queueForMails
->setRecipient($profile->getAttribute('email', ''))
->setName($profile->getAttribute('name'))
->setName($profile->getAttribute('name', ''))
->setBody($body)
->setVariables($emailVariables)
->setSubject($subject)
@@ -2546,7 +2576,8 @@ App::put('/v1/account/recovery')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
->action(function (string $userId, string $secret, string $password, string $passwordAgain, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents) {
->inject('hooks')
->action(function (string $userId, string $secret, string $password, string $passwordAgain, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks) {
if ($password !== $passwordAgain) {
throw new Exception(Exception::USER_PASSWORD_MISMATCH);
}
@@ -2580,6 +2611,8 @@ App::put('/v1/account/recovery')
$history = array_slice($history, (count($history) - $historyLimit), $historyLimit);
}
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
@@ -2597,7 +2630,7 @@ App::put('/v1/account/recovery')
* the recovery token but actually we don't need it anymore.
*/
$dbForProject->deleteDocument('tokens', $recovery);
$dbForProject->deleteCachedDocument('users', $profile->getId());
$dbForProject->purgeCachedDocument('users', $profile->getId());
$queueForEvents
->setParam('userId', $profile->getId())
@@ -2668,7 +2701,7 @@ App::post('/v1/account/verification')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
@@ -2681,7 +2714,7 @@ App::post('/v1/account/verification')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{body}}', $body)
->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
->setParam('{{footer}}', $locale->getText("emails.verification.footer"))
->setParam('{{thanks}}', $locale->getText("emails.verification.thanks"))
@@ -2736,11 +2769,11 @@ App::post('/v1/account/verification')
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */
'user' => $user->getAttribute('name'),
'team' => '',
'project' => $projectName,
'redirect' => $url
'redirect' => $url,
'project' => $projectName
];
$queueForMails
@@ -2817,7 +2850,7 @@ App::put('/v1/account/verification')
* the verification token but actually we don't need it anymore.
*/
$dbForProject->deleteDocument('tokens', $verification);
$dbForProject->deleteCachedDocument('users', $profile->getId());
$dbForProject->purgeCachedDocument('users', $profile->getId());
$queueForEvents
->setParam('userId', $userId)
@@ -2829,8 +2862,9 @@ App::put('/v1/account/verification')
App::post('/v1/account/verification/phone')
->desc('Create phone verification')
->groups(['api', 'account'])
->groups(['api', 'account', 'auth'])
->label('scope', 'account')
->label('auth.type', 'phone')
->label('event', 'users.[userId].verification.[tokenId].create')
->label('audits.event', 'verification.create')
->label('audits.resource', 'user/{response.userId}')
@@ -2842,7 +2876,7 @@ App::post('/v1/account/verification/phone')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'userId:{userId}')
->label('abuse-key', ['url:{url},userId:{userId}', 'url:{url},ip:{ip}'])
->inject('request')
->inject('response')
->inject('user')
@@ -2891,7 +2925,7 @@ App::post('/v1/account/verification/phone')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
@@ -2906,6 +2940,7 @@ App::post('/v1/account/verification/phone')
$queueForMessaging
->setRecipient($user->getAttribute('phone'))
->setMessage($message)
->setProject($project)
->trigger()
;
@@ -2974,7 +3009,7 @@ App::put('/v1/account/verification/phone')
* We act like we're updating and validating the verification token but actually we don't need it anymore.
*/
$dbForProject->deleteDocument('tokens', $verification);
$dbForProject->deleteCachedDocument('users', $profile->getId());
$dbForProject->purgeCachedDocument('users', $profile->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -2983,3 +3018,52 @@ App::put('/v1/account/verification/phone')
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
});
App::delete('/v1/account')
->desc('Delete account')
->groups(['api', 'account'])
->label('event', 'users.[userId].delete')
->label('scope', 'account')
->label('audits.event', 'user.delete')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'account')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/account/delete.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->inject('user')
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForDeletes')
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
if ($project->getId() === 'console') {
// get all memberships
$memberships = $user->getAttribute('memberships', []);
foreach ($memberships as $membership) {
// prevent deletion if at least one active membership
if ($membership->getAttribute('confirm', false)) {
throw new Exception(Exception::USER_DELETION_PROHIBITED);
}
}
}
$dbForProject->deleteDocument('users', $user->getId());
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($user);
$queueForEvents
->setParam('userId', $user->getId())
->setPayload($response->output($user, Response::MODEL_USER));
$response->noContent();
});
+1 -1
View File
@@ -116,7 +116,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Throwable $err) {
$index = 0;
do {
+46 -19
View File
@@ -153,13 +153,13 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
} catch (LimitException) {
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
} catch (\Exception $e) {
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
throw $e;
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) {
$twoWayKey = $options['twoWayKey'];
@@ -197,13 +197,13 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
$dbForProject->deleteDocument('attributes', $attribute->getId());
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
} catch (\Exception $e) {
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
$dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
throw $e;
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
$dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
}
$queueForDatabase
@@ -358,7 +358,7 @@ function updateAttribute(
$relatedOptions = \array_merge($relatedAttribute->getAttribute('options'), $options);
$relatedAttribute->setAttribute('options', $relatedOptions);
$dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey'], $relatedAttribute);
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
}
} else {
$dbForProject->updateAttribute(
@@ -371,7 +371,7 @@ function updateAttribute(
}
$attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collection->getId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collection->getId());
$queueForEvents
->setContext('collection', $collection)
@@ -703,8 +703,8 @@ App::delete('/v1/databases/:databaseId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB');
}
$dbForProject->deleteCachedDocument('databases', $database->getId());
$dbForProject->deleteCachedCollection('databases_' . $database->getInternalId());
$dbForProject->purgeCachedDocument('databases', $database->getId());
$dbForProject->purgeCachedCollection('databases_' . $database->getInternalId());
$queueForDatabase
->setType(DATABASE_TYPE_DELETE_DATABASE)
@@ -1076,7 +1076,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove collection from DB');
}
$dbForProject->deleteCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
$dbForProject->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
$queueForDatabase
->setType(DATABASE_TYPE_DELETE_COLLECTION)
@@ -2248,8 +2248,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
$attribute = $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting'));
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
$options = $attribute->getAttribute('options');
@@ -2270,8 +2270,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'deleting'));
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $options['relatedCollection']);
$dbForProject->deleteCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $options['relatedCollection']);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
}
}
@@ -2453,7 +2453,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
throw new Exception(Exception::INDEX_ALREADY_EXISTS);
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$queueForDatabase
->setType(DATABASE_TYPE_CREATE_INDEX)
@@ -2620,7 +2620,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$index = $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting'));
}
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$queueForDatabase
->setType(DATABASE_TYPE_DELETE_INDEX)
@@ -2997,6 +2997,33 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$processDocument($collection, $document);
}
$select = \array_reduce($queries, function ($result, $query) {
return $result || ($query->getMethod() === Query::TYPE_SELECT);
}, false);
// Check if the SELECT query includes $databaseId and $collectionId
$hasDatabaseId = false;
$hasCollectionId = false;
if ($select) {
$hasDatabaseId = \array_reduce($queries, function ($result, $query) {
return $result || ($query->getMethod() === Query::TYPE_SELECT && \in_array('$databaseId', $query->getValues()));
}, false);
$hasCollectionId = \array_reduce($queries, function ($result, $query) {
return $result || ($query->getMethod() === Query::TYPE_SELECT && \in_array('$collectionId', $query->getValues()));
}, false);
}
if ($select) {
foreach ($documents as $document) {
if (!$hasDatabaseId) {
$document->removeAttribute('$databaseId');
}
if (!$hasCollectionId) {
$document->removeAttribute('$collectionId');
}
}
}
$response->dynamic(new Document([
'total' => $total,
'documents' => $documents,
+1 -1
View File
@@ -456,7 +456,7 @@ App::get('/v1/functions/:functionId/usage')
->label('sdk.method', 'getFunctionUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTIONS)
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
+146 -10
View File
@@ -7,6 +7,7 @@ use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\Pools\Group;
use Utopia\Queue\Client;
use Utopia\Queue\Connection;
@@ -14,8 +15,11 @@ use Utopia\Registry\Registry;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\Validator\Domain;
use Utopia\Validator\Integer;
use Utopia\Validator\Multiple;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::get('/v1/health')
->desc('Get HTTP')
@@ -355,7 +359,7 @@ App::get('/v1/health/queue/webhooks')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -382,12 +386,62 @@ App::get('/v1/health/queue/logs')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "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/certificate')
->desc('Get the SSL certificate for a domain')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getCertificate')
->label('sdk.description', '/docs/references/health/get-certificate.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_CERTIFICATE)
->param('domain', null, new Multiple([new Domain(), new PublicDomain()]), Multiple::TYPE_STRING, 'Domain name')
->inject('response')
->action(function (string $domain, Response $response) {
if (filter_var($domain, FILTER_VALIDATE_URL)) {
$domain = parse_url($domain, PHP_URL_HOST);
}
$sslContext = stream_context_create([
"ssl" => [
"capture_peer_cert" => true
]
]);
$sslSocket = stream_socket_client("ssl://" . $domain . ":443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $sslContext);
if (!$sslSocket) {
throw new Exception(Exception::HEALTH_INVALID_HOST);
}
$streamContextParams = stream_context_get_params($sslSocket);
$peerCertificate = $streamContextParams['options']['ssl']['peer_certificate'];
$certificatePayload = openssl_x509_parse($peerCertificate);
$sslExpiration = $certificatePayload['validTo_time_t'];
$status = $sslExpiration < time() ? 'fail' : 'pass';
if ($status === 'fail') {
throw new Exception(Exception::HEALTH_CERTIFICATE_EXPIRED);
}
$response->dynamic(new Document([
'name' => $certificatePayload['name'],
'subjectSN' => $certificatePayload['subject']['CN'],
'issuerOrganisation' => $certificatePayload['issuer']['O'],
'validFrom' => $certificatePayload['validFrom_time_t'],
'validTo' => $certificatePayload['validTo_time_t'],
'signatureTypeSN' => $certificatePayload['signatureTypeSN'],
]), Response::MODEL_HEALTH_CERTIFICATE);
}, ['response']);
App::get('/v1/health/queue/certificates')
->desc('Get certificates queue')
->groups(['api', 'health'])
@@ -409,7 +463,7 @@ App::get('/v1/health/queue/certificates')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -436,7 +490,7 @@ App::get('/v1/health/queue/builds')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -464,7 +518,7 @@ App::get('/v1/health/queue/databases')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -491,7 +545,7 @@ App::get('/v1/health/queue/deletes')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -518,7 +572,7 @@ App::get('/v1/health/queue/mails')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -545,7 +599,7 @@ App::get('/v1/health/queue/messaging')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -572,7 +626,7 @@ App::get('/v1/health/queue/migrations')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -599,7 +653,7 @@ App::get('/v1/health/queue/functions')
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
@@ -648,6 +702,47 @@ App::get('/v1/health/storage/local')
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
});
App::get('/v1/health/storage')
->desc('Get storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getStorage')
->label('sdk.description', '/docs/references/health/get-storage.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->inject('deviceFiles')
->inject('deviceFunctions')
->inject('deviceBuilds')
->action(function (Response $response, Device $deviceFiles, Device $deviceFunctions, Device $deviceBuilds) {
$devices = [$deviceFiles, $deviceFunctions, $deviceBuilds];
$checkStart = \microtime(true);
foreach ($devices as $device) {
if (!$device->write($device->getPath('health.txt'), 'test', 'text/plain')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed writing test file to ' . $device->getRoot());
}
if ($device->read($device->getPath('health.txt')) !== 'test') {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed reading test file from ' . $device->getRoot());
}
if (!$device->delete($device->getPath('health.txt'))) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed deleting test file from ' . $device->getRoot());
}
}
$output = [
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
];
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
});
App::get('/v1/health/anti-virus')
->desc('Get antivirus')
->groups(['api', 'health'])
@@ -687,6 +782,47 @@ App::get('/v1/health/anti-virus')
$response->dynamic(new Document($output), Response::MODEL_HEALTH_ANTIVIRUS);
});
App::get('/v1/health/queue/failed/:name')
->desc('Get number of failed queue jobs')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getFailedJobs')
->param('name', '', new WhiteList([
Event::DATABASE_QUEUE_NAME,
Event::DELETE_QUEUE_NAME,
Event::AUDITS_QUEUE_NAME,
Event::MAILS_QUEUE_NAME,
Event::FUNCTIONS_QUEUE_NAME,
Event::USAGE_QUEUE_NAME,
Event::WEBHOOK_CLASS_NAME,
Event::CERTIFICATES_QUEUE_NAME,
Event::BUILDS_QUEUE_NAME,
Event::MESSAGING_QUEUE_NAME,
Event::MIGRATIONS_QUEUE_NAME,
Event::HAMSTER_CLASS_NAME
]), 'The name of the queue')
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->label('sdk.description', '/docs/references/health/get-failed-queue-jobs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->inject('queue')
->action(function (string $name, int|string $threshold, Response $response, Connection $queue) {
$threshold = \intval($threshold);
$client = new Client($name, $queue);
$failed = $client->countFailedJobs();
if ($failed >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue failed jobs threshold hit. Current size is {$failed} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $failed ]), Response::MODEL_HEALTH_QUEUE);
});
App::get('/v1/health/stats') // Currently only used internally
->desc('Get system stats')
->groups(['api', 'health'])
+22 -1
View File
@@ -144,9 +144,30 @@ App::get('/v1/project/usage')
];
}, $dbForProject->find('buckets'));
// merge network inbound + outbound
$projectBandwidth = [];
foreach ($usage[METRIC_NETWORK_INBOUND] as $item) {
$projectBandwidth[$item['date']] ??= 0;
$projectBandwidth[$item['date']] += $item['value'];
}
foreach ($usage[METRIC_NETWORK_OUTBOUND] as $item) {
$projectBandwidth[$item['date']] ??= 0;
$projectBandwidth[$item['date']] += $item['value'];
}
$network = [];
foreach ($projectBandwidth as $date => $value) {
$network[] = [
'date' => $date,
'value' => $value
];
}
$response->dynamic(new Document([
'requests' => ($usage[METRIC_NETWORK_REQUESTS]),
'network' => ($usage[METRIC_NETWORK_INBOUND] + $usage[METRIC_NETWORK_OUTBOUND]),
'network' => $network,
'users' => ($usage[METRIC_USERS]),
'executions' => ($usage[METRIC_EXECUTIONS]),
'executionsTotal' => $total[METRIC_EXECUTIONS],
+67 -44
View File
@@ -9,6 +9,7 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\ProjectId;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Abuse\Adapters\TimeLimit;
@@ -71,12 +72,12 @@ App::post('/v1/projects')
->param('legalCity', '', new Text(256), 'Project legal City. Max length: 256 chars.', true)
->param('legalAddress', '', new Text(256), 'Project legal Address. Max length: 256 chars.', true)
->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true)
->inject('request')
->inject('response')
->inject('dbForConsole')
->inject('cache')
->inject('pools')
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Cache $cache, Group $pools) {
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools) {
$team = $dbForConsole->getDocument('teams', $teamId);
@@ -85,8 +86,15 @@ 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];
foreach ($auth as $index => $method) {
$auths = [
'limit' => 0,
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
'passwordHistory' => 0,
'passwordDictionary' => false,
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
'personalDataCheck' => false
];
foreach ($auth as $method) {
$auths[$method['key'] ?? ''] = true;
}
@@ -121,7 +129,7 @@ App::post('/v1/projects')
}
}
$databaseOverride = App::getEnv('_APP_DATABASE_OVERRIDE', null);
$databaseOverride = App::getEnv('_APP_DATABASE_OVERRIDE');
$index = array_search($databaseOverride, $databases);
if ($index !== false) {
$database = $databases[$index];
@@ -133,6 +141,23 @@ App::post('/v1/projects')
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
// TODO: One in 20 projects use shared tables. Temporary until all projects are using shared tables.
if (
!\mt_rand(0, 19)
&& App::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted'
) {
$database = DATABASE_SHARED_TABLES;
}
// TODO: Allow overriding in development mode. Temporary until all projects are using shared tables.
if (
App::isDevelopment()
&& App::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted'
&& $request->getHeader('x-appwrite-share-tables', false)
) {
$database = DATABASE_SHARED_TABLES;
}
try {
$project = $dbForConsole->createDocument('projects', new Document([
'$id' => $projectId,
@@ -164,14 +189,26 @@ App::post('/v1/projects')
'keys' => null,
'auths' => $auths,
'search' => implode(' ', [$projectId, $name]),
'database' => $database
'database' => $database,
]));
} catch (Duplicate $th) {
} catch (Duplicate) {
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
}
$dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache);
$dbForProject->setNamespace("_{$project->getInternalId()}");
if ($database === DATABASE_SHARED_TABLES) {
$dbForProject
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$dbForProject
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
$dbForProject->create();
$audit = new Audit($dbForProject);
@@ -188,33 +225,19 @@ App::post('/v1/projects')
continue;
}
$attributes = [];
$indexes = [];
$attributes = \array_map(function (array $attribute) {
return new Document($attribute);
}, $collection['attributes']);
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => $attribute['$id'],
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
'default' => $attribute['default'] ?? null,
'format' => $attribute['format'] ?? ''
]);
}
$indexes = \array_map(function (array $index) {
return new Document($index);
}, $collection['indexes']);
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => $index['$id'],
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
try {
$dbForProject->createCollection($key, $attributes, $indexes);
} catch (Duplicate) {
// Collection already exists
}
$dbForProject->createCollection($key, $attributes, $indexes);
}
$response
@@ -820,7 +843,7 @@ App::post('/v1/projects/:projectId/webhooks')
$webhook = $dbForConsole->createDocument('webhooks', $webhook);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -941,7 +964,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
->setAttribute('httpPass', $httpPass);
$dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
@@ -980,7 +1003,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
$webhook->setAttribute('signatureKey', \bin2hex(\random_bytes(64)));
$dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
@@ -1017,7 +1040,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
$dbForConsole->deleteDocument('webhooks', $webhook->getId());
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->noContent();
});
@@ -1067,7 +1090,7 @@ App::post('/v1/projects/:projectId/keys')
$key = $dbForConsole->createDocument('keys', $key);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -1181,7 +1204,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->dynamic($key, Response::MODEL_KEY);
});
@@ -1218,7 +1241,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
$dbForConsole->deleteDocument('keys', $key->getId());
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->noContent();
});
@@ -1268,7 +1291,7 @@ App::post('/v1/projects/:projectId/platforms')
$platform = $dbForConsole->createDocument('platforms', $platform);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -1383,7 +1406,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
$dbForConsole->updateDocument('platforms', $platform->getId(), $platform);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->dynamic($platform, Response::MODEL_PLATFORM);
});
@@ -1420,7 +1443,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
$dbForConsole->deleteDocument('platforms', $platformId);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->noContent();
});
@@ -1446,7 +1469,7 @@ App::patch('/v1/projects/:projectId/smtp')
->param('port', 587, new Integer(), 'SMTP server port', true)
->param('username', '', new Text(0, 0), 'SMTP server username', true)
->param('password', '', new Text(0, 0), 'SMTP server password', true)
->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true)
->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true)
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole) {
@@ -1590,7 +1613,7 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
$message
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'))
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escapeHtml: false)
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature"))
->setParam('{{direction}}', $localeObj->getText('settings.direction'));
+10 -1
View File
@@ -14,6 +14,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Logger\Log;
use Utopia\Validator\Domain as ValidatorDomain;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -278,7 +279,8 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
->inject('queueForEvents')
->inject('project')
->inject('dbForConsole')
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole) {
->inject('log')
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole, Log $log) {
$rule = $dbForConsole->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
@@ -298,7 +300,14 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
$validator = new CNAME($target->get()); // Verify Domain with DNS records
$domain = new Domain($rule->getAttribute('domain', ''));
$validationStart = \microtime(true);
if (!$validator->isValid($domain->get())) {
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
$log->addTag('dnsDomain', $domain->get());
$error = $validator->getLogs();
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
}
+60 -27
View File
@@ -44,6 +44,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\DSN\DSN;
use Utopia\Swoole\Request;
use Utopia\Storage\Compression\Compression;
App::post('/v1/storage/buckets')
->desc('Create bucket')
@@ -66,7 +67,7 @@ App::post('/v1/storage/buckets')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0), new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response')
@@ -237,7 +238,7 @@ App::put('/v1/storage/buckets/:bucketId')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', null, new Range(1, (int) App::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)App::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true)
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', COMPRESSION_TYPE_NONE, new WhiteList([COMPRESSION_TYPE_NONE, COMPRESSION_TYPE_GZIP, COMPRESSION_TYPE_ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . COMPRESSION_TYPE_NONE . ', [' . COMPRESSION_TYPE_GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . COMPRESSION_TYPE_ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response')
@@ -531,19 +532,24 @@ App::post('/v1/storage/buckets/:bucketId/files')
$fileHash = $deviceFiles->getFileHash($path); // Get file hash before compression and encryption
$data = '';
// Compression
$algorithm = $bucket->getAttribute('compression', COMPRESSION_TYPE_NONE);
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != COMPRESSION_TYPE_NONE) {
$algorithm = $bucket->getAttribute('compression', Compression::NONE);
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != Compression::NONE) {
$data = $deviceFiles->read($path);
switch ($algorithm) {
case COMPRESSION_TYPE_ZSTD:
case Compression::ZSTD:
$compressor = new Zstd();
break;
case COMPRESSION_TYPE_GZIP:
case Compression::GZIP:
default:
$compressor = new GZIP();
break;
}
$data = $compressor->compress($data);
} else {
// reset the algorithm to none as we do not compress the file
// if file size exceedes the APP_STORAGE_READ_BUFFER
// regardless the bucket compression algoorithm
$algorithm = Compression::NONE;
}
if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) {
@@ -615,7 +621,17 @@ App::post('/v1/storage/buckets/:bucketId/files')
->setAttribute('metadata', $metadata)
->setAttribute('chunksUploaded', $chunksUploaded);
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
/**
* Validate create permission and skip authorization in updateDocument
* Without this, the file creation will fail when user doesn't have update permission
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
*/
$validator = new Authorization(Database::PERMISSION_CREATE);
if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
@@ -652,7 +668,17 @@ App::post('/v1/storage/buckets/:bucketId/files')
->setAttribute('chunksUploaded', $chunksUploaded)
->setAttribute('metadata', $metadata);
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
/**
* Validate create permission and skip authorization in updateDocument
* Without this, the file creation will fail when user doesn't have update permission
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
*/
$validator = new Authorization(Database::PERMISSION_CREATE);
if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
@@ -859,14 +885,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg';
}
$inputs = Config::getParam('storage-inputs');
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
@@ -877,9 +895,17 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg';
}
$inputs = Config::getParam('storage-inputs');
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
$path = $file->getAttribute('path');
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
$algorithm = $file->getAttribute('algorithm', 'none');
$algorithm = $file->getAttribute('algorithm', Compression::NONE);
$cipher = $file->getAttribute('openSSLCipher');
$mime = $file->getAttribute('mimeType');
if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) App::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) {
@@ -890,7 +916,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$path = $fileLogos['default_image'];
}
$algorithm = 'none';
$algorithm = Compression::NONE;
$cipher = null;
$background = (empty($background)) ? 'eceff1' : $background;
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
@@ -902,12 +928,17 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
}
if (empty($output)) {
// when file extension is provided but it's not one of our
// supported outputs we fallback to `jpg`
if (!empty($type) && !array_key_exists($type, $outputs)) {
$type = 'jpg';
}
// when file extension is not provided and the mime type is not one of our supported outputs
// we fallback to `jpg` output format
$output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type;
}
$source = $deviceFiles->read($path);
if (!empty($cipher)) { // Decrypt
@@ -922,11 +953,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
}
switch ($algorithm) {
case 'zstd':
case Compression::ZSTD:
$compressor = new Zstd();
$source = $compressor->decompress($source);
break;
case 'gzip':
case Compression::GZIP:
$compressor = new GZIP();
$source = $compressor->decompress($source);
break;
@@ -1065,15 +1096,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
);
}
switch ($file->getAttribute('algorithm', 'none')) {
case 'zstd':
switch ($file->getAttribute('algorithm', Compression::NONE)) {
case Compression::ZSTD:
if (empty($source)) {
$source = $deviceFiles->read($path);
}
$compressor = new Zstd();
$source = $compressor->decompress($source);
break;
case 'gzip':
case Compression::GZIP:
if (empty($source)) {
$source = $deviceFiles->read($path);
}
@@ -1214,15 +1245,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
);
}
switch ($file->getAttribute('algorithm', 'none')) {
case 'zstd':
switch ($file->getAttribute('algorithm', Compression::NONE)) {
case Compression::ZSTD:
if (empty($source)) {
$source = $deviceFiles->read($path);
}
$compressor = new Zstd();
$source = $compressor->decompress($source);
break;
case 'gzip':
case Compression::GZIP:
if (empty($source)) {
$source = $deviceFiles->read($path);
}
@@ -1236,10 +1267,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$response->send(substr($source, $start, ($end - $start + 1)));
}
$response->send($source);
return;
}
if (!empty($rangeHeader)) {
$response->send($deviceFiles->read($path, $start, ($end - $start + 1)));
return;
}
$size = $deviceFiles->getFileSize($path);
+15 -15
View File
@@ -114,7 +114,7 @@ App::post('/v1/teams')
]);
$membership = $dbForProject->createDocument('memberships', $membership);
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
}
$queueForEvents->setParam('teamId', $team->getId());
@@ -533,10 +533,10 @@ App::post('/v1/teams/:teamId/memberships')
} catch (Duplicate $th) {
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
}
$team->setAttribute('total', $team->getAttribute('total', 0) + 1);
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
$dbForProject->deleteCachedDocument('users', $invitee->getId());
Authorization::skip(fn() => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$dbForProject->purgeCachedDocument('users', $invitee->getId());
} else {
try {
$membership = $dbForProject->createDocument('memberships', $membership);
@@ -556,7 +556,7 @@ App::post('/v1/teams/:teamId/memberships')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{body}}', $body)
->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
@@ -612,11 +612,11 @@ App::post('/v1/teams/:teamId/memberships')
$emailVariables = [
'owner' => $user->getAttribute('name'),
'direction' => $locale->getText('settings.direction'),
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */
'user' => $user->getAttribute('name'),
'team' => $team->getAttribute('name'),
'project' => $projectName,
'redirect' => $url
'redirect' => $url,
'project' => $projectName
];
$queueForMails
@@ -641,6 +641,7 @@ App::post('/v1/teams/:teamId/memberships')
$queueForMessaging
->setRecipient($phone)
->setMessage($message)
->setProject($project)
->trigger();
}
}
@@ -843,7 +844,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
/**
* Replace membership on profile
*/
$dbForProject->deleteCachedDocument('users', $profile->getId());
$dbForProject->purgeCachedDocument('users', $profile->getId());
$queueForEvents
->setParam('teamId', $team->getId())
@@ -958,15 +959,15 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Permission::delete(Role::user($user->getId())),
]));
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
Authorization::setRole(Role::user($userId)->toString());
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1)));
Authorization::skip(fn() => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$queueForEvents
->setParam('teamId', $team->getId())
@@ -1043,11 +1044,10 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB');
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0));
Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
Authorization::skip(fn() => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
}
$queueForEvents
+51 -26
View File
@@ -9,6 +9,7 @@ use Appwrite\Event\Event;
use Appwrite\Network\Validator\Email;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Utopia\CLI\Console;
use Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Queries\Users;
use Utopia\Database\Validator\Query\Limit;
@@ -39,10 +40,12 @@ use Utopia\Validator\Integer;
use Appwrite\Auth\Validator\PasswordHistory;
use Appwrite\Auth\Validator\PasswordDictionary;
use Appwrite\Auth\Validator\PersonalData;
use Appwrite\Hooks\Hooks;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents): Document
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks): Document
{
$plaintextPassword = $password;
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
@@ -65,13 +68,13 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
if ($project->getAttribute('auths', [])['personalDataCheck'] ?? false) {
$personalDataValidator = new PersonalData($userId, $email, $name, $phone);
if (!$personalDataValidator->isValid($password)) {
if (!$personalDataValidator->isValid($plaintextPassword)) {
throw new Exception(Exception::USER_PASSWORD_PERSONAL_DATA);
}
}
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$user = $dbForProject->createDocument('users', new Document([
$user = new Document([
'$id' => $userId,
'$permissions' => [
Permission::read(Role::any()),
@@ -97,7 +100,13 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $phone, $name]),
]));
]);
if ($hash === 'plaintext') {
$hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]);
}
$user = $dbForProject->createDocument('users', $user);
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
@@ -130,9 +139,9 @@ App::post('/v1/users')
->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) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents);
->inject('hooks')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
@@ -160,8 +169,9 @@ App::post('/v1/users/bcrypt')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents);
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -190,8 +200,9 @@ App::post('/v1/users/md5')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents);
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -220,8 +231,9 @@ App::post('/v1/users/argon2')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents);
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -251,14 +263,15 @@ App::post('/v1/users/sha')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$options = '{}';
if (!empty($passwordVersion)) {
$options = '{"version":"' . $passwordVersion . '"}';
}
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents);
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -287,8 +300,9 @@ App::post('/v1/users/phpass')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents);
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -322,7 +336,8 @@ App::post('/v1/users/scrypt')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$options = [
'salt' => $passwordSalt,
'costCpu' => $passwordCpu,
@@ -331,7 +346,7 @@ App::post('/v1/users/scrypt')
'length' => $passwordLength
];
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents);
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -363,8 +378,9 @@ App::post('/v1/users/scrypt-modified')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents);
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -846,7 +862,8 @@ App::patch('/v1/users/:userId/password')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
->inject('hooks')
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = $dbForProject->getDocument('users', $userId);
@@ -861,6 +878,8 @@ App::patch('/v1/users/:userId/password')
}
}
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
@@ -1089,7 +1108,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
}
$dbForProject->deleteDocument('sessions', $session->getId());
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -1132,7 +1151,7 @@ App::delete('/v1/users/:userId/sessions')
//TODO: fix this
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -1184,7 +1203,7 @@ App::delete('/v1/users/:userId')
});
App::delete('/v1/users/identities/:identityId')
->desc('Delete Identity')
->desc('Delete identity')
->groups(['api', 'users'])
->label('event', 'users.[userId].identities.[identityId].delete')
->label('scope', 'users.write')
@@ -1199,7 +1218,8 @@ App::delete('/v1/users/identities/:identityId')
->param('identityId', '', new UID(), 'Identity ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $identityId, Response $response, Database $dbForProject) {
->inject('queueForEvents')
->action(function (string $identityId, Response $response, Database $dbForProject, Event $queueForEvents) {
$identity = $dbForProject->getDocument('identities', $identityId);
@@ -1209,6 +1229,11 @@ App::delete('/v1/users/identities/:identityId')
$dbForProject->deleteDocument('identities', $identityId);
$queueForEvents
->setParam('userId', $identity->getAttribute('userId'))
->setParam('identityId', $identity->getId())
->setPayload($response->output($identity, Response::MODEL_IDENTITY));
return $response->noContent();
});
+278 -105
View File
@@ -9,9 +9,8 @@ use Utopia\Logger\Logger;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Swoole\Http\Request as SwooleRequest;
use Utopia\Cache\Cache;
use Utopia\Pools\Group;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\View;
use Appwrite\Extend\Exception as AppwriteException;
@@ -19,6 +18,8 @@ use Utopia\Config\Config;
use Utopia\Domains\Domain;
use Appwrite\Auth\Auth;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
use Appwrite\Event\Usage;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Response\Filters\V11 as ResponseV11;
use Appwrite\Utopia\Response\Filters\V12 as ResponseV12;
@@ -31,6 +32,7 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\Hostname;
use Appwrite\Utopia\Request\Filters\V12 as RequestV12;
@@ -38,6 +40,7 @@ use Appwrite\Utopia\Request\Filters\V13 as RequestV13;
use Appwrite\Utopia\Request\Filters\V14 as RequestV14;
use Appwrite\Utopia\Request\Filters\V15 as RequestV15;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Executor\Executor;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -45,7 +48,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleRequest, Request $request, Response $response)
function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Reader $geodb)
{
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
@@ -117,59 +120,218 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
$path .= '?' . $query;
}
$body = $swooleRequest->getContent() ?? '';
$method = $swooleRequest->server['request_method'];
$requestHeaders = $request->getHeaders();
$body = \json_encode([
'async' => false,
'body' => $swooleRequest->getContent() ?? '',
'method' => $swooleRequest->server['request_method'],
'path' => $path,
'headers' => $requestHeaders
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND);
}
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
if (\is_null($runtime)) {
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
}
if ($deployment->isEmpty()) {
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
}
/** Check if build has completed */
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
throw new AppwriteException(AppwriteException::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'ready') {
throw new AppwriteException(AppwriteException::BUILD_NOT_READY);
}
$permissions = $function->getAttribute('execute');
if (!(\in_array('any', $permissions)) && (\in_array('guests', $permissions))) {
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
}
$headers = \array_merge([], $requestHeaders);
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = '';
$headers['x-appwrite-user-jwt'] = '';
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
$headers['x-appwrite-continent-eu'] = 'false';
$ip = $headers['x-real-ip'] ?? '';
if (!empty($ip)) {
$record = $geodb->get($ip);
if ($record) {
$eu = Config::getParam('locale-eu');
$headers['x-appwrite-country-code'] = $record['country']['iso_code'] ?? '';
$headers['x-appwrite-continent-code'] = $record['continent']['code'] ?? '';
$headers['x-appwrite-continent-eu'] = (\in_array($record['country']['iso_code'], $eu)) ? 'true' : 'false';
}
}
$headersFiltered = [];
foreach ($headers as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
$headersFiltered[] = ['name' => $key, 'value' => $value];
}
}
$executionId = ID::unique();
$execution = new Document([
'$id' => $executionId,
'$permissions' => [],
'functionInternalId' => $function->getInternalId(),
'functionId' => $function->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'trigger' => 'http', // http / schedule / event
'status' => 'processing', // waiting / processing / completed / failed
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
'requestMethod' => $method,
'requestHeaders' => $headersFiltered,
'errors' => '',
'logs' => '',
'duration' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
]);
$headers = [
'Content-Type: application/json',
'Content-Length: ' . \strlen($body),
'X-Appwrite-Project: ' . $projectId
];
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->setContext('function', $function);
$ch = \curl_init();
\curl_setopt($ch, CURLOPT_URL, "http://localhost/v1/functions/{$functionId}/executions");
\curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
\curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
\curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// \curl_setopt($ch, CURLOPT_HEADER, true);
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
\curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$durationStart = \microtime(true);
$executionResponse = \curl_exec($ch);
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = \curl_error($ch);
$errNo = \curl_errno($ch);
$vars = [];
\curl_close($ch);
if ($errNo !== 0) {
throw new AppwriteException(AppwriteException::GENERAL_ARGUMENT_INVALID, "Internal error: " . $error);
// V2 vars
if ($version === 'v2') {
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_DATA' => $body ?? '',
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
]);
}
if ($statusCode >= 400) {
$error = \json_decode($executionResponse, true)['message'];
throw new AppwriteException(AppwriteException::GENERAL_ARGUMENT_INVALID, "Execution error: " . $error);
// Shared vars
foreach ($function->getAttribute('varsProject', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$execution = \json_decode($executionResponse, true);
// Function vars
foreach ($function->getAttribute('vars', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$contentType = 'text/plain';
foreach ($execution['responseHeaders'] as $header) {
if (\strtolower($header['name']) === 'content-type') {
$contentType = $header['value'];
// Appwrite vars
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
]);
/** Execute function */
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
$command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"';
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $build->getAttribute('path', ''),
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
requestTimeout: 30
);
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
$headersFiltered[] = ['name' => $key, 'value' => $value];
}
}
$response->setHeader($header['name'], $header['value']);
/** Update execution status */
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
$execution->setAttribute('status', $status);
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
$durationEnd = \microtime(true);
$execution
->setAttribute('duration', $durationEnd - $durationStart)
->setAttribute('status', 'failed')
->setAttribute('responseStatusCode', 500)
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
Console::error($th->getMessage());
} finally {
$queueForUsage
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
;
}
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
$execution->setAttribute('logs', '');
$execution->setAttribute('errors', '');
$headers = [];
foreach (($executionResponse['headers'] ?? []) as $key => $value) {
$headers[] = ['name' => $key, 'value' => $value];
}
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
$execution->setAttribute('responseHeaders', $headers);
$body = $execution['responseBody'] ?? '';
$encodingKey = \array_search('x-open-runtimes-encoding', \array_column($execution['responseHeaders'], 'name'));
@@ -179,6 +341,15 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
}
}
$contentType = 'text/plain';
foreach ($execution['responseHeaders'] as $header) {
if (\strtolower($header['name']) === 'content-type') {
$contentType = $header['value'];
}
$response->setHeader($header['name'], $header['value']);
}
$response
->setContentType($contentType)
->setStatusCode($execution['responseStatusCode'] ?? 200)
@@ -205,13 +376,17 @@ App::init()
->inject('console')
->inject('project')
->inject('dbForConsole')
->inject('getProjectDB')
->inject('user')
->inject('locale')
->inject('localeCodes')
->inject('clients')
->inject('servers')
->inject('queueForCertificates')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $localeCodes, array $clients, array $servers, Certificate $queueForCertificates) {
->inject('queueForEvents')
->inject('queueForUsage')
->inject('geodb')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Document $user, Locale $locale, array $localeCodes, array $clients, array $servers, Certificate $queueForCertificates, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) {
/*
* Appwrite Router
*/
@@ -220,7 +395,7 @@ App::init()
$mainDomain = App::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain) {
if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) {
return;
}
}
@@ -232,7 +407,9 @@ App::init()
Request::setRoute($route);
if ($route === null) {
return $response->setStatusCode(404)->send('Not Found');
return $response
->setStatusCode(404)
->send('Not Found');
}
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
@@ -425,7 +602,7 @@ App::init()
->addHeader('Server', 'Appwrite')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Share-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true');
@@ -511,7 +688,7 @@ App::init()
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DateTime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
}
$sdkValidator = new WhiteList($servers, true);
@@ -525,7 +702,7 @@ App::init()
/** Update access time as well */
$key->setAttribute('accessedAt', Datetime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
}
}
}
@@ -571,7 +748,11 @@ App::options()
->inject('request')
->inject('response')
->inject('dbForConsole')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole) {
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('geodb')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) {
/*
* Appwrite Router
*/
@@ -579,7 +760,7 @@ App::options()
$mainDomain = App::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain) {
if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) {
return;
}
}
@@ -589,7 +770,7 @@ App::options()
$response
->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Share-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
@@ -603,66 +784,58 @@ App::error()
->inject('response')
->inject('project')
->inject('logger')
->inject('loggerBreadcrumbs')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
->inject('log')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute();
$publish = true;
if ($error instanceof AppwriteException) {
$publish = $error->isPublishable();
} else {
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
}
if ($logger && $publish) {
if ($error->getCode() >= 500 || $error->getCode() === 0) {
try {
/** @var Utopia\Database\Document $user */
$user = $utopia->getResource('user');
} catch (\Throwable $th) {
// All good, user is optional information for logger
}
$log = new Utopia\Logger\Log();
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('database', $project->getAttribute('database', 'console'));
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
if ($logger && ($publish || $error->getCode() === 0)) {
try {
/** @var Utopia\Database\Document $user */
$user = $utopia->getResource('user');
} catch (\Throwable $th) {
// All good, user is optional information for logger
}
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('database', $project->getAttribute('database', 'console'));
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
$code = $error->getCode();
@@ -765,10 +938,10 @@ App::error()
->setParam('development', App::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $error->getMessage())
->setParam('type', $type)
->setParam('code', $code)
->setParam('trace', $trace);
->setParam('message', $output['message'] ?? '')
->setParam('type', $output['type'] ?? '')
->setParam('code', $output['code'] ?? '')
->setParam('trace', $output['trace'] ?? []);
$response->html($layout->render());
}
+26 -3
View File
@@ -177,6 +177,7 @@ App::init()
$end = $request->getContentRangeEnd();
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{projectId}', $project->getId())
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
@@ -335,7 +336,7 @@ App::init()
break;
case 'magic-url':
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
if (($auths['usersAuthMagicURL'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
}
break;
@@ -346,6 +347,12 @@ App::init()
}
break;
case 'phone':
if (($auths['phone'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
}
break;
case 'invites':
if (($auths['invites'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
@@ -359,7 +366,7 @@ App::init()
break;
default:
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication type: ' . $route->getLabel('auth.type', ''));
break;
}
});
@@ -400,7 +407,7 @@ App::shutdown()
$session = array_shift($sessions);
$dbForProject->deleteDocument('sessions', $session->getId());
}
$dbForProject->deleteCachedDocument('users', $userId);
$dbForProject->purgeCachedDocument('users', $userId);
});
App::shutdown()
@@ -592,6 +599,22 @@ App::shutdown()
->setProject($project)
->trigger();
}
/**
* Update user last activity
*/
if (!$user->isEmpty()) {
$accessedAt = $user->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCCESS)) > $accessedAt) {
$user->setAttribute('accessedAt', DateTime::now());
if (APP_MODE_ADMIN !== $mode) {
$dbForProject->updateDocument('users', $user->getId(), $user);
} else {
$dbForConsole->updateDocument('users', $user->getId(), $user);
}
}
}
});
App::init()
+7 -1
View File
@@ -32,7 +32,7 @@ App::init()
break;
case 'magic-url':
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
if (($auths['usersAuthMagicURL'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
}
break;
@@ -43,6 +43,12 @@ App::init()
}
break;
case 'phone':
if (($auths['phone'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
}
break;
case 'invites':
if (($auths['invites'] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
+2 -7
View File
@@ -147,7 +147,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
$dbForConsole->createCollection($key, $attributes, $indexes);
}
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDefaultDatabase(), 'bucket_1')) {
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
Console::success('[Setup] - Creating default bucket...');
$dbForConsole->createDocument('buckets', new Document([
'$id' => ID::custom('default'),
@@ -263,10 +263,9 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
// All good, user is optional information for logger
}
$loggerBreadcrumbs = $app->getResource("loggerBreadcrumbs");
$route = $app->getRoute();
$log = new Utopia\Logger\Log();
$log = $app->getResource("log");
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
@@ -298,10 +297,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
+170 -155
View File
@@ -12,11 +12,11 @@ if (\file_exists(__DIR__ . '/../vendor/autoload.php')) {
require_once __DIR__ . '/../vendor/autoload.php';
}
ini_set('memory_limit', '512M');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('default_socket_timeout', -1);
error_reporting(E_ALL);
\ini_set('memory_limit', '512M');
\ini_set('display_errors', 1);
\ini_set('display_startup_errors', 1);
\ini_set('default_socket_timeout', -1);
\error_reporting(E_ALL);
use Appwrite\Event\Migration;
use Appwrite\Event\Usage;
@@ -34,6 +34,7 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\URL\URL as AppwriteURL;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Logger\Logger;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
@@ -76,11 +77,13 @@ use Appwrite\Hooks\Hooks;
use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Database\PDOProxy;
use Utopia\Logger\Log;
use Utopia\Queue;
use Utopia\Queue\Connection;
use Utopia\Storage\Storage;
use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub;
use Utopia\Validator\Range;
use Utopia\Validator\Hostname;
use Utopia\Validator\IP;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
@@ -110,7 +113,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
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 = 328;
const APP_CACHE_BUSTER = 331;
const APP_VERSION_STABLE = '1.4.13';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
@@ -140,7 +143,8 @@ const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
const APP_HOSTNAME_INTERNAL = 'appwrite';
// Database Reconnect
// Database
const DATABASE_SHARED_TABLES = 'database_db_self_hosted_shared_tables';
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
// Database Worker Types
@@ -174,10 +178,6 @@ const DELETE_TYPE_SESSIONS = 'sessions';
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
const DELETE_TYPE_SCHEDULES = 'schedules';
// Compression type
const COMPRESSION_TYPE_NONE = 'none';
const COMPRESSION_TYPE_GZIP = 'gzip';
const COMPRESSION_TYPE_ZSTD = 'zstd';
// Mail Types
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
@@ -197,6 +197,7 @@ const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
const METRIC_MESSAGES = 'messages';
const METRIC_SESSIONS = 'sessions';
const METRIC_DATABASES = 'databases';
const METRIC_COLLECTIONS = 'collections';
@@ -655,14 +656,13 @@ $register->set('pools', function () {
foreach ($connections as $key => $connection) {
$type = $connection['type'] ?? '';
$dsns = $connection['dsns'] ?? '';
$multipe = $connection['multiple'] ?? false;
$multiple = $connection['multiple'] ?? false;
$schemes = $connection['schemes'] ?? [];
$config = [];
$dsns = explode(',', $connection['dsns'] ?? '');
foreach ($dsns as &$dsn) {
$dsn = explode('=', $dsn);
$name = ($multipe) ? $key . '_' . $dsn[0] : $key;
$name = ($multiple) ? $key . '_' . $dsn[0] : $key;
$dsn = $dsn[1] ?? '';
$config[] = $name;
if (empty($dsn)) {
@@ -685,47 +685,38 @@ $register->set('pools', function () {
/**
* Get Resource
*
* Creation could be reused accross connection types like database, cache, queue, etc.
* Creation could be reused across connection types like database, cache, queue, etc.
*
* Resource assignment to an adapter will happen below.
*/
switch ($dsnScheme) {
case 'mysql':
case 'mariadb':
$resource = function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true
));
});
};
break;
case 'redis':
$resource = function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
if ($dsnPass) {
$redis->auth($dsnPass);
}
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
$resource = match ($dsnScheme) {
'mysql',
'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true
));
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
if ($dsnPass) {
$redis->auth($dsnPass);
}
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
return $redis;
};
break;
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid scheme");
break;
}
return $redis;
},
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid scheme"),
};
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
// Get Adapter
$adapter = null;
switch ($type) {
case 'database':
$adapter = match ($dsn->getScheme()) {
@@ -734,7 +725,7 @@ $register->set('pools', function () {
default => null
};
$adapter->setDefaultDatabase($dsn->getPath());
$adapter->setDatabase($dsn->getPath());
break;
case 'pubsub':
$adapter = $resource();
@@ -754,7 +745,6 @@ $register->set('pools', function () {
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
break;
}
return $adapter;
@@ -771,22 +761,22 @@ $register->set('pools', function () {
$register->set('db', function () {
// This is usually for our workers or CLI commands scope
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbPort = App::getEnv('_APP_DB_PORT', '');
$dbUser = App::getEnv('_APP_DB_USER', '');
$dbPass = App::getEnv('_APP_DB_PASS', '');
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbPort = App::getEnv('_APP_DB_PORT', '');
$dbUser = App::getEnv('_APP_DB_USER', '');
$dbPass = App::getEnv('_APP_DB_PASS', '');
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$pdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true,
));
$pdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true,
));
return $pdo;
return $pdo;
});
$register->set('smtp', function () {
@@ -867,6 +857,7 @@ foreach ($locales as $locale) {
]);
// Runtime Execution
App::setResource('log', fn() => new Log());
App::setResource('logger', function ($register) {
return $register->get('logger');
}, ['register']);
@@ -875,10 +866,6 @@ App::setResource('hooks', function ($register) {
return $register->get('hooks');
}, ['register']);
App::setResource('loggerBreadcrumbs', function () {
return [];
});
App::setResource('register', fn() => $register);
App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en')));
@@ -931,6 +918,21 @@ App::setResource('clients', function ($request, $console, $project) {
'hostname' => $request->getHostname(),
], Document::SET_TYPE_APPEND);
$hostnames = explode(',', App::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
$validator = new Hostname();
foreach ($hostnames as $hostname) {
$hostname = trim($hostname);
if (!$validator->isValid($hostname)) {
continue;
}
$console->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Origin::CLIENT_TYPE_WEB,
'name' => $hostname,
'hostname' => $hostname,
], Document::SET_TYPE_APPEND);
}
/**
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
@@ -939,24 +941,25 @@ App::setResource('clients', function ($request, $console, $project) {
fn ($node) => $node['hostname'],
\array_filter(
$console->getAttribute('platforms', []),
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && isset($node['hostname']) && !empty($node['hostname']))
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
)
);
$clients = \array_unique(
\array_merge(
$clientsConsole,
\array_map(
fn ($node) => $node['hostname'],
\array_filter(
$project->getAttribute('platforms', []),
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB || $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && isset($node['hostname']) && !empty($node['hostname']))
)
)
)
);
$clients = $clientsConsole;
$platforms = $project->getAttribute('platforms', []);
return $clients;
foreach ($platforms as $node) {
if (
isset($node['type']) &&
($node['type'] === Origin::CLIENT_TYPE_WEB ||
$node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
!empty($node['hostname'])
) {
$clients[] = $node['hostname'];
}
}
return \array_unique($clients);
}, ['request', 'console', 'project']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) {
@@ -1128,11 +1131,22 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
$database = new Database($dbAdapter, $cache);
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$database
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
return $database;
}, ['pools', 'dbForConsole', 'cache', 'project']);
@@ -1140,8 +1154,7 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
$dbAdapter = $pools
->get('console')
->pop()
->getResource()
;
->getResource();
$database = new Database($dbAdapter, $cache);
@@ -1157,22 +1170,35 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
$getProjectDB = function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
}
$databaseName = $project->getAttribute('database');
if (isset($databases[$databaseName])) {
$database = $databases[$databaseName];
$configure = (function (Database $database) use ($project) {
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$database
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
});
if (isset($databases[$databaseName])) {
$database = $databases[$databaseName];
$configure($database);
return $database;
}
@@ -1182,19 +1208,10 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$databaseName] = $database;
$database
->setNamespace('_' . $project->getInternalId())
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
$configure($database);
return $database;
};
return $getProjectDB;
}, ['pools', 'dbForConsole', 'cache']);
App::setResource('cache', function (Group $pools) {
@@ -1251,62 +1268,60 @@ function getDevice($root): Device
Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
}
switch ($device) {
case Storage::DEVICE_S3:
return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case STORAGE::DEVICE_DO_SPACES:
return new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case Storage::DEVICE_BACKBLAZE:
return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case Storage::DEVICE_LINODE:
return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case Storage::DEVICE_WASABI:
return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
}
return match ($device) {
Storage::DEVICE_S3 => new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl),
Storage::DEVICE_DO_SPACES => new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl),
Storage::DEVICE_BACKBLAZE => new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl),
Storage::DEVICE_LINODE => new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl),
Storage::DEVICE_WASABI => new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl),
default => new Local($root),
};
} else {
switch (strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) {
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
case Storage::DEVICE_S3:
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
$s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', '');
$s3Region = App::getEnv('_APP_STORAGE_S3_REGION', '');
$s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', '');
$s3Acl = 'private';
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
case Storage::DEVICE_DO_SPACES:
$doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
$doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
$doSpacesAcl = 'private';
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
case Storage::DEVICE_BACKBLAZE:
$backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
$backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
$backblazeRegion = App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
$backblazeBucket = App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
$backblazeAcl = 'private';
return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
case Storage::DEVICE_LINODE:
$linodeAccessKey = App::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
$linodeSecretKey = App::getEnv('_APP_STORAGE_LINODE_SECRET', '');
$linodeRegion = App::getEnv('_APP_STORAGE_LINODE_REGION', '');
$linodeBucket = App::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
$linodeAcl = 'private';
return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
case Storage::DEVICE_WASABI:
$wasabiAccessKey = App::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
$wasabiSecretKey = App::getEnv('_APP_STORAGE_WASABI_SECRET', '');
$wasabiRegion = App::getEnv('_APP_STORAGE_WASABI_REGION', '');
$wasabiBucket = App::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
$wasabiAcl = 'private';
return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
}
$device = strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '');
return match ($device) {
Storage::DEVICE_S3 => new S3(
$root,
App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''),
App::getEnv('_APP_STORAGE_S3_SECRET', ''),
App::getEnv('_APP_STORAGE_S3_BUCKET', ''),
App::getEnv('_APP_STORAGE_S3_REGION', ''),
'private'
),
Storage::DEVICE_DO_SPACES => new DOSpaces(
$root,
App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''),
App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''),
App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''),
App::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''),
'private'
),
Storage::DEVICE_BACKBLAZE => new Backblaze(
$root,
App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''),
App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''),
App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''),
App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''),
'private'
),
Storage::DEVICE_LINODE => new Linode(
$root,
App::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', ''),
App::getEnv('_APP_STORAGE_LINODE_SECRET', ''),
App::getEnv('_APP_STORAGE_LINODE_BUCKET', ''),
App::getEnv('_APP_STORAGE_LINODE_REGION', ''),
'private'
),
Storage::DEVICE_WASABI => new Wasabi(
$root,
App::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', ''),
App::getEnv('_APP_STORAGE_WASABI_SECRET', ''),
App::getEnv('_APP_STORAGE_WASABI_BUCKET', ''),
App::getEnv('_APP_STORAGE_WASABI_REGION', ''),
'private'
),
default => new Local($root),
};
}
}
+5 -3
View File
@@ -78,6 +78,7 @@ services:
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
- _APP_CONSOLE_HOSTNAMES
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
@@ -142,6 +143,7 @@ services:
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_DELAY
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
@@ -268,7 +270,7 @@ services:
depends_on:
- redis
- mariadb
volumes:
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
@@ -415,7 +417,7 @@ services:
depends_on:
- redis
- mariadb
volumes:
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
environment:
@@ -643,7 +645,7 @@ services:
- _APP_DB_PASS
appwrite-assistant:
image: appwrite/assistant:0.2.2
image: appwrite/assistant:0.3.0
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped
+62 -18
View File
@@ -9,10 +9,12 @@ use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Func;
use Appwrite\Event\Hamster;
use Appwrite\Event\Mail;
use Appwrite\Event\Migration;
use Appwrite\Event\Phone;
use Appwrite\Event\Usage;
use Appwrite\Event\UsageDump;
use Appwrite\Platform\Appwrite;
use Swoole\Runtime;
use Utopia\App;
@@ -61,15 +63,31 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
}
$pools = $register->get('pools');
$database = $pools
$adapter = $pools
->get($project->getAttribute('database'))
->pop()
->getResource()
;
->getResource();
$adapter = new Database($database, $cache);
$adapter->setNamespace('_' . $project->getInternalId());
return $adapter;
$database = new Database($adapter, $cache);
$database
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$database
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
return $database;
}, ['cache', 'register', 'message', 'dbForConsole']);
Server::setResource('project', function (Message $message, Database $dbForConsole) {
@@ -95,7 +113,19 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
if (isset($databases[$databaseName])) {
$database = $databases[$databaseName];
$database->setNamespace('_' . $project->getInternalId());
if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$database
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
return $database;
}
@@ -108,7 +138,17 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
$databases[$databaseName] = $database;
$database->setNamespace('_' . $project->getInternalId());
if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
} else {
$database
->setShareTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
return $database;
};
@@ -145,6 +185,9 @@ Server::setResource('log', fn() => new Log());
Server::setResource('queueForUsage', function (Connection $queue) {
return new Usage($queue);
}, ['queue']);
Server::setResource('queueForUsageDump', function (Connection $queue) {
return new UsageDump($queue);
}, ['queue']);
Server::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
}, ['pools']);
@@ -178,6 +221,9 @@ Server::setResource('queueForCertificates', function (Connection $queue) {
Server::setResource('queueForMigrations', function (Connection $queue) {
return new Migration($queue);
}, ['queue']);
Server::setResource('queueForHamster', function (Connection $queue) {
return new Hamster($queue);
}, ['queue']);
Server::setResource('logger', function (Registry $register) {
return $register->get('logger');
}, ['register']);
@@ -227,7 +273,7 @@ try {
* Any worker can be configured with the following env vars:
* - _APP_WORKERS_NUM The total number of worker processes
* - _APP_WORKER_PER_CORE The number of worker processes per core (ignored if _APP_WORKERS_NUM is set)
* - _APP_QUEUE_NAME The name of the queue to read for database events
* - _APP_QUEUE_NAME The name of the queue to read for database events
*/
if ($workerName === 'databases') {
$queueName = App::getEnv('_APP_QUEUE_NAME', 'database_db_main');
@@ -245,7 +291,6 @@ try {
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
}
$worker = $platform->getWorker();
$worker
@@ -260,7 +305,8 @@ $worker
->inject('error')
->inject('logger')
->inject('log')
->action(function (Throwable $error, ?Logger $logger, Log $log) {
->inject('project')
->action(function (Throwable $error, ?Logger $logger, Log $log, Document $project) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
if ($error instanceof PDOException) {
@@ -276,6 +322,7 @@ $worker
$log->setAction('appwrite-queue-' . App::getEnv('QUEUE'));
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId() ?? 'n/a');
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
@@ -295,12 +342,9 @@ $worker
Console::error('[Error] Line: ' . $error->getLine());
});
try {
$workerStart = $worker->getWorkerStart();
} catch (\Throwable $error) {
$worker->workerStart();
} finally {
Console::info("Worker $workerName started");
}
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->start();
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=failed $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=processing $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=success $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-retry $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php usage $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php hamster $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php usage-dump $@
+10 -9
View File
@@ -1,4 +1,5 @@
{
"name": "appwrite/server-ce",
"description": "End to end backend server for frontend and mobile apps.",
"type": "project",
@@ -43,26 +44,26 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.13.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.33.*",
"utopia-php/abuse": "0.34.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.35.*",
"utopia-php/cache": "0.8.*",
"utopia-php/audit": "0.36.*",
"utopia-php/cache": "0.9.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.45.*",
"utopia-php/domains": "0.3.*",
"utopia-php/database": "0.45.7",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.1.*",
"utopia-php/framework": "0.31.0",
"utopia-php/image": "0.5.*",
"utopia-php/framework": "0.33.*",
"utopia-php/image": "0.6.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.2.*",
"utopia-php/messaging": "0.3.*",
"utopia-php/migration": "0.3.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.5.*",
"utopia-php/pools": "0.4.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.6.*",
"utopia-php/queue": "0.7.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.5.*",
Generated
+229 -230
View File
File diff suppressed because it is too large Load Diff
+119 -33
View File
@@ -5,12 +5,12 @@
x-logging: &x-logging
logging:
driver: 'json-file'
driver: "json-file"
options:
max-file: '5'
max-size: '10m'
max-file: "5"
max-size: "10m"
version: '3'
version: "3"
services:
traefik:
@@ -53,7 +53,7 @@ services:
DEBUG: false
TESTING: true
VERSION: dev
ports:
ports:
- 9501:80
networks:
- appwrite
@@ -88,7 +88,7 @@ services:
- mariadb
- redis
# - clamav
entrypoint:
entrypoint:
- php
- -e
- app/http.php
@@ -100,6 +100,7 @@ services:
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_WHITELIST_IPS
- _APP_CONSOLE_HOSTNAMES
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
@@ -191,7 +192,7 @@ services:
container_name: appwrite-realtime
image: appwrite-dev
restart: unless-stopped
ports:
ports:
- 9505:80
labels:
- "traefik.enable=true"
@@ -299,7 +300,7 @@ services:
depends_on:
- redis
- mariadb
volumes:
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
@@ -353,7 +354,7 @@ services:
image: appwrite-dev
networks:
- appwrite
volumes:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
@@ -452,7 +453,7 @@ services:
depends_on:
- redis
- mariadb
volumes:
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- ./app:/usr/src/code/app
@@ -570,6 +571,7 @@ services:
- _APP_REDIS_PASS
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_SMS_PROJECTS_DENY_LIST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@@ -643,6 +645,7 @@ services:
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_MAINTENANCE_DELAY
appwrite-worker-usage:
entrypoint: worker-usage
@@ -673,6 +676,38 @@ services:
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-worker-usage-dump:
entrypoint: worker-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-schedule:
entrypoint: schedule
@@ -703,12 +738,69 @@ services:
appwrite-assistant:
container_name: appwrite-assistant
image: appwrite/assistant:0.2.2
image: appwrite/assistant:0.3.0
networks:
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-worker-hamster:
entrypoint: worker-hamster
<<: *x-logging
container_name: appwrite-worker-hamster
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_MIXPANEL_TOKEN
appwrite-hamster-scheduler:
entrypoint: hamster
<<: *x-logging
container_name: appwrite-hamster-scheduler
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_HAMSTER_TIME
- _APP_HAMSTER_INTERVAL
openruntimes-executor:
container_name: openruntimes-executor
hostname: appwrite-executor
@@ -797,7 +889,7 @@ services:
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests
command: "mysqld --innodb-flush-method=fsync" # add ' --query_cache_size=0' for DB tests
# command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu
# smtp:
@@ -843,15 +935,16 @@ services:
#
# MailCatcher - An SMTP server. Catches all system emails and displays them in a nice UI.
# RequestCatcher - An HTTP server. Catches all system https calls and displays them using a simple HTTP API. Used to debug & tests webhooks and HTTP tasks
# RedisCommander - A nice UI for exploring Redis data
# Webgrind - A nice UI for exploring and debugging code-level stuff
# Redis Insight - A nice UI for exploring Redis data
# Adminer - A nice UI for exploring MariaDB data
# GraphQl Explorer - A nice UI for exploring GraphQL API
maildev: # used mainly for dev tests
image: appwrite/mailcatcher:1.0.0
container_name: appwrite-mailcatcher
<<: *x-logging
ports:
- '9503:1080'
- "9503:1080"
networks:
- appwrite
@@ -860,7 +953,7 @@ services:
container_name: appwrite-requestcatcher
<<: *x-logging
ports:
- '9504:5000'
- "9504:5000"
networks:
- appwrite
@@ -874,22 +967,16 @@ services:
networks:
- appwrite
# redis-commander:
# image: rediscommander/redis-commander:latest
# restart: unless-stopped
# networks:
# - appwrite
# environment:
# - REDIS_HOSTS=redis
# ports:
# - "8081:8081"
# webgrind:
# image: 'jokkedk/webgrind:latest'
# volumes:
# - './debug:/tmp'
# ports:
# - '3001:80'
redis-insight:
image: redis/redisinsight:latest
restart: unless-stopped
networks:
- appwrite
environment:
- REDIS_HOSTS=redis
ports:
- "8081:5540"
graphql-explorer:
container_name: appwrite-graphql-explorer
image: appwrite/altair:0.3.0
@@ -901,7 +988,6 @@ services:
environment:
- SERVER_URL=http://localhost/v1/graphql
# Dev Tools End ------------------------------------------------------------------------------------------
networks:
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.create_phone_verification()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.create_recovery(email: 'email@example.com', url: 'https://example.com')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.create_verification(url: 'https://example.com')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.delete_identity(identity_id: '[IDENTITY_ID]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.delete_session(session_id: '[SESSION_ID]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.delete_sessions()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.get_prefs()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.get_session(session_id: '[SESSION_ID]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.get()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.list_identities()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.list_logs()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.list_sessions()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_email(email: 'email@example.com', password: 'password')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_name(name: '[NAME]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_password(password: '')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_phone_verification(user_id: '[USER_ID]', secret: '[SECRET]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_phone(phone: '+12065550100', password: 'password')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_prefs(prefs: {})
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_recovery(user_id: '[USER_ID]', secret: '[SECRET]', password: 'password', password_again: 'password')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_session(session_id: '[SESSION_ID]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_status()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_verification(user_id: '[USER_ID]', secret: '[SECRET]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_browser(code: 'aa')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_credit_card(code: 'amex')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_favicon(url: 'https://example.com')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_flag(code: 'af')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_image(url: 'https://example.com')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_initials()
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_qr(text: '[TEXT]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_boolean_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', name: '[NAME]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_datetime_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]', data: {})
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_email_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_enum_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', elements: [], required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_float_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', type: 'key', attributes: [])
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_integer_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_ip_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_relationship_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', related_collection_id: '[RELATED_COLLECTION_ID]', type: 'oneToOne')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_string_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', size: 1, required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_url_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create(database_id: '[DATABASE_ID]', name: '[NAME]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]')
puts response.inspect
puts response.inspect
@@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
puts response.inspect
puts response.inspect

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