Compare commits

...
Author SHA1 Message Date
Jake Barnby 503fb2ca82 Debug 2024-03-07 12:49:54 +01:00
Jake Barnby 1db9d8b9ab Fix method refs 2024-03-07 10:40:03 +01:00
Jake Barnby 1ffb80f2f7 Fix realtime db 2024-03-07 10:37:57 +01:00
Jake Barnby 2bed17de35 Debug 2024-03-06 21:50:31 +01:00
Jake Barnby 3312ed1c5d Debug 2024-03-06 21:14:55 +01:00
Jake Barnby 1b7ce8fc61 Debug 2024-03-06 10:59:05 +01:00
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
Matej BačoandGitHub 3abb53d3cb Merge pull request #7301 from appwrite/fix-rename-stats
rename stats collection
2023-12-18 12:06:55 +01:00
Damodar Lohani caa5c4e744 making visibility private 2023-12-18 10:20:00 +00:00
Damodar Lohani 1f558442d9 linter fix 2023-12-18 10:18:40 +00:00
Damodar Lohani 5e59d73f78 rename stats collection 2023-12-18 10:11:10 +00: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
Matej Bačo 0aaab63b8c Remove var dumps 2023-12-15 23:37:31 +01:00
Matej Bačo 8c1363529e Add Hooks 2023-12-15 23:19:43 +01: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
Christy JacobandGitHub b36c98af66 Merge pull request #7274 from appwrite/refactor-usage-execution-trigger
Refactor usage execution trigger
2023-12-14 06:16:01 +01:00
德华 刘 dc89501012 chore: linter 2023-12-14 04:51:36 +00:00
德华 刘 1307f73c31 chore: refactor code 2023-12-14 04:49:16 +00:00
Christy JacobandGitHub 39d91aecf1 Merge pull request #7273 from appwrite/damodar/pea-15-refactor-maintenance-deletes-worker
PEA-15 Refactor Deletes and maintenance worker
2023-12-14 05:08:11 +01: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
Torsten Dittmann 9bc52e8900 fix: commatation in email html 2023-12-13 18:08:36 +01:00
Torsten Dittmann 06cd61e53a Merge branch 'refactor-usage-sn' of https://github.com/appwrite/appwrite into refactor-usage-sn 2023-12-13 17:47:37 +01:00
Torsten Dittmann 7645d6c34a fix: email templates 2023-12-13 17:46:14 +01: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
Matej Bačo cc67bcba28 Add group for cloud limit check 2023-12-12 21:33:02 +01:00
shimon 41ae96ea77 change execution usage trigger 2023-12-12 21:55:01 +02:00
shimon bdf9a64771 change execution usage trigger 2023-12-12 21:21:47 +02:00
Torsten Dittmann cf87cfab3a Merge branch 'refactor-usage-sn' of https://github.com/appwrite/appwrite into refactor-usage-sn 2023-12-12 18:48:26 +01:00
Torsten Dittmann 063db1e2bf feat: add execution aggregation to project usage 2023-12-12 18:48:21 +01:00
Matej Bačo fc504e3a68 Change collections priority for pricing override 2023-12-12 12:27:04 +01:00
Damodar Lohani 7891f9e74c refactor deletes worker 2023-12-12 11:26:44 +00:00
Damodar Lohani 5512123e04 loop through projects 2023-12-12 11:26:28 +00:00
Damodar Lohani 5c3bd741b4 fix resources 2023-12-12 11:26:14 +00:00
Damodar Lohani d8fe96e300 set project resource 2023-12-12 11:08:14 +00:00
Damodar LohaniandGitHub e5fb743943 Merge pull request #7272 from appwrite/damodar/fla-127-make-maintenance-worker-aware-of-the-projects-plan 2023-12-12 10:47:52 +01:00
Damodar Lohani 9ac57e2e7a remove unused retention 2023-12-12 08:11:01 +00:00
Damodar Lohani 104b8bb366 provide retention time as queue server resource 2023-12-12 08:07:24 +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
Torsten Dittmann 3d081e1aa5 fix: allow billingPlan query on teams 2023-12-11 19:48:52 +01:00
Torsten Dittmann 5e74c5bbc4 fix: typo usage 2023-12-11 17:12:43 +01:00
Torsten Dittmann 3485373cd7 chore: generate specs 2023-12-11 16:40:19 +01:00
Torsten Dittmann 7d5f926d9c Merge branch 'refactor-usage-sn' of https://github.com/appwrite/appwrite into refactor-usage-sn 2023-12-11 16:37:27 +01:00
Torsten Dittmann 2657247b50 bump: console version 2023-12-11 16:37:14 +01:00
Torsten DittmannandGitHub dc8e8a5002 Merge pull request #7270 from appwrite/feat-usage-project-breakdown
feat: usage breakdown by project
2023-12-11 16:34:06 +01:00
Torsten Dittmann 1794b1395a chore: run formatter 2023-12-11 16:33:15 +01:00
Torsten Dittmann ba75f69969 feat: usage breakdown by project 2023-12-11 16:19:08 +01:00
Torsten Dittmann b87231153e chore: bump console version number 2023-12-10 21:38:34 +01:00
Torsten Dittmann f8878ac039 chore: generate specs 2023-12-10 21:29:32 +01:00
Torsten Dittmann c37e31e6c3 fix: inconsistent timestamp generation 2023-12-10 20:58:38 +01:00
Torsten DittmannandGitHub 6925122791 Merge pull request #7266 from appwrite/feat-project-usage-custom-daterange
feat: project usage custom date range
2023-12-10 12:35:58 +01:00
Damodar LohaniandGitHub 1836e89dff Merge pull request #7251 from appwrite/feat-mail-template-override-ur 2023-12-10 11:46:25 +01:00
Torsten Dittmann 95425832ae fix: tests and response models 2023-12-10 11:03:01 +01:00
Torsten Dittmann bb934080ba feat: project usage custom date range 2023-12-10 10:26:33 +01:00
Damodar Lohani ffdc662022 fix formatting 2023-12-09 06:41:24 +00:00
Damodar LohaniandGitHub e4467521f8 Merge branch 'refactor-usage-sn' into feat-mail-template-override-ur 2023-12-09 07:36:11 +01: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
Damodar LohaniandGitHub 3b7fa3d664 Merge pull request #7262 from appwrite/feat-mail-worker-attachment-support
fix redis issue by encoding content
2023-12-08 22:44:38 +01:00
Damodar Lohani e3bab623f1 fix redis issue by encoding content 2023-12-08 21:34:39 +00:00
Damodar LohaniandGitHub a9a15e947b Merge pull request #7261 from appwrite/feat-mail-worker-attachment-support 2023-12-08 21:23:44 +01:00
Damodar Lohani f553576d0e fix linter 2023-12-08 20:06:37 +00:00
Christy Jacob 8b5783d3ff fix: update setParam 2023-12-08 21:02:07 +01:00
Damodar Lohani 386cee5137 mail support string as attachment 2023-12-08 19:57:15 +00: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
Damodar LohaniandGitHub 5c9199f332 fix empty body template 2023-12-06 16:16:31 +01:00
Damodar LohaniandGitHub 83d4de9f73 support maile template override 2023-12-06 14:52:13 +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
shimon f16aa598e2 console::billing 2023-11-30 19:51:51 +02:00
shimon fce2055000 console::billing 2023-11-30 19:50:13 +02: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
shimon 235e31e6d6 composer.lock 2023-11-30 17:02:46 +02:00
Bradley Schofield 8bec64b2a2 Update Hamster.php 2023-11-30 11:05:15 +00:00
shimon d624481193 tests fix 2023-11-30 10:22:34 +02:00
shimon c313e8347f Merge branch '1.4.x' of github.com:appwrite/appwrite into refactor-usage-sn
 Conflicts:
	composer.lock
2023-11-30 10:21:59 +02:00
shimon 5daae1db14 tests fix 2023-11-30 10:20:14 +02:00
shimon 30d5e99a07 Merge branch 'main' of github.com:appwrite/appwrite into refactor-usage-sn 2023-11-30 10:18:48 +02: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
Jake BarnbyandGitHub 1a496bbf71 Merge pull request #7180 from appwrite/chore-update-sdks
Chore update sdks
2023-11-27 15:04:48 +13:00
shimon c9a4eb0a49 tests fix 2023-11-26 22:57:57 +02: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
shimon 7ee96fb118 sync against 1.4x 2023-11-26 10:36:52 +02:00
shimon 21895e3ac0 Merge branch '1.4.x' of github.com:appwrite/appwrite into refactor-usage-sn
 Conflicts:
	.gitmodules
	CHANGES.md
	Dockerfile
	app/controllers/api/storage.php
	app/init.php
	composer.lock
	src/Appwrite/Migration/Migration.php
	src/Appwrite/Platform/Services/Tasks.php
	src/Appwrite/Usage/Calculators/TimeSeries.php
2023-11-26 10:28:17 +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
Torsten DittmannandGitHub 540ff6ee2e Merge pull request #7189 from appwrite/chore-upgrade-console-3.2.9
chore: upgrade console to 3.2.9
2023-11-21 12:57:32 +01:00
Torsten Dittmann 5179c4d08a chore: upgrade console to 3.2.9 2023-11-21 12:08:26 +01:00
Jake BarnbyandGitHub 6201edc65d Merge pull request #7183 from appwrite/chore-migration-enum-attribute-size
migrates enum attribute size to 255
2023-11-21 22:38:25 +13:00
prateek banga 1720acb2b4 lint fix 2023-11-21 12:32:30 +05:30
aa6a1a2e31 Update src/Appwrite/Migration/Version/V19.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2023-11-21 12:24:08 +05:30
prateek banga 022f0a663e changes array search for cursorAfter query 2023-11-21 12:23:36 +05:30
prateek banga 7e625ba653 adds queries support in documentsIterator 2023-11-21 11:48:27 +05:30
Prateek BangaandGitHub 72b4c78c9f Merge branch '1.4.x' into chore-migration-enum-attribute-size 2023-11-21 11:06:05 +05:30
Christy JacobandGitHub 0dca07f6fc Merge pull request #7187 from appwrite/get-migration-stats
feat: add migration stats task
2023-11-20 20:28:31 -05:00
Christy Jacob 39df6e443a Merge branch 'get-migration-stats' of github.com:appwrite/appwrite into get-migration-stats 2023-11-20 20:26:51 -05:00
Christy Jacob 8eb8351d5f feat: add migration counts to hamster 2023-11-20 20:26:38 -05:00
9d31d8d950 Update src/Appwrite/Platform/Tasks/GetMigrationStats.php
Co-authored-by: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com>
2023-11-20 20:10:56 -05:00
Christy Jacob f81d675609 feat: run linter 2023-11-20 20:10:24 -05:00
Christy Jacob 28c61acec6 feat: update migration task 2023-11-20 19:39:07 -05:00
Christy Jacob 9a12ab8f8a feat: add migration stats task 2023-11-20 19:36:53 -05:00
prateek banga da3798e52f review changes 2023-11-20 23:31:34 +05:30
prateek banga b2381798ab migrates enum attribute size to 255 2023-11-20 22:01:20 +05:30
Jake Barnby 8b14d96151 Merge remote-tracking branch 'origin/1.4.x' into chore-update-sdks 2023-11-21 00:17:36 +13:00
Jake Barnby 6ad4566b6b Update CLI 2023-11-21 00:15:50 +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
Christy JacobandGitHub a7833421ef Merge pull request #7168 from appwrite/feat-1.4.13-release-prep
Prepare 1.4.13 release
2023-11-17 17:46:35 -05:00
Steven Nguyen 37e1e3adaf Add 1.4.13 to CHANGES.md 2023-11-17 20:12:11 +00:00
Steven Nguyen a96e571258 Bump appwrite version to 1.4.13 2023-11-17 20:11:56 +00:00
Christy JacobandGitHub 91e29fb974 Merge pull request #7167 from appwrite/chore-bump-console-3.2.8
Bump console to version 3.2.8
2023-11-17 15:07:44 -05:00
Steven Nguyen 926e1f3fc8 Bump console to version 3.2.8 2023-11-17 19:34:33 +00:00
Christy JacobandGitHub 81902f0555 Merge pull request #7142 from appwrite/ci-composer-validate
Add a CI task to validate composer file and lock
2023-11-17 14:26:35 -05:00
Christy JacobandGitHub f9783ccba3 Merge pull request #7155 from appwrite/fix-error-logging
Add flag to send logs to logger
2023-11-17 14:02:18 -05:00
Khushboo Verma 695d6f4065 Renamed variables 2023-11-18 00:04:57 +05:30
Khushboo Verma 03e8a29e8d Merge branch '1.4.x' into fix-error-logging 2023-11-18 00:02:03 +05:30
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
Christy JacobandGitHub e16387e463 Merge pull request #7154 from appwrite/fix-rate-limit-error-on-large-uploads
Add chunkId to abuse key to prevent rate limit for SDKs
2023-11-17 13:00:27 -05:00
Khushboo VermaandGitHub a76efe7d4e Merge branch '1.4.x' into fix-rate-limit-error-on-large-uploads 2023-11-17 21:19:27 +05:30
Torsten DittmannandGitHub 0873093441 Merge pull request #7164 from appwrite/bug-add-enum-longer-length
changes enum size validation in update controller
2023-11-17 16:39:59 +01:00
Prateek BangaandGitHub 446e1c0979 Merge branch '1.4.x' into bug-add-enum-longer-length 2023-11-17 20:59:31 +05:30
Prateek Banga b68be491b9 updated description 2023-11-17 20:59:09 +05:30
Prateek Banga 9183f2a875 changes enum size validation in update controller 2023-11-17 20:56:18 +05:30
Khushboo Verma 2a2ff34e20 Merge branch '1.4.x' into fix-error-logging 2023-11-17 20:37:36 +05:30
Torsten DittmannandGitHub f0e900a3b5 Merge pull request #7163 from appwrite/bug-add-enum-longer-length
fixes test case
2023-11-17 15:54:53 +01:00
Prateek Banga 55df51fe94 Merge branch 'bug-add-enum-longer-length' of https://github.com/appwrite/appwrite into bug-add-enum-longer-length 2023-11-17 20:15:10 +05:30
Prateek Banga ab35b75c37 fix test message 2023-11-17 20:14:55 +05:30
Prateek Banga 2a54fd64ee Merge branch '1.4.x' of https://github.com/appwrite/appwrite into bug-add-enum-longer-length 2023-11-17 20:08:19 +05:30
Prateek BangaandGitHub 274ef1664b Merge branch '1.4.x' into bug-add-enum-longer-length 2023-11-17 20:08:13 +05:30
Prateek Banga 228b813994 fixes test case 2023-11-17 20:06:28 +05:30
Khushboo Verma 93f58fb876 Merge branch '1.4.x' into fix-error-logging 2023-11-17 19:30:58 +05:30
Khushboo Verma 7902defe69 Addressed PR comments 2023-11-17 19:29:57 +05:30
Torsten DittmannandGitHub 505091c9e7 Merge pull request #7162 from appwrite/bug-add-enum-longer-length
Bug add enum longer length
2023-11-17 14:57:24 +01:00
Prateek Banga 827ad84f53 fixes adding longer length enum 2023-11-17 19:09:31 +05:30
cf1c914edb Update app/controllers/general.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2023-11-17 18:36:03 +05:30
9329287982 Update app/controllers/general.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2023-11-17 18:35:51 +05:30
40ec98569e Update app/controllers/general.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2023-11-17 18:35:26 +05:30
Christy JacobandGitHub eeb0eed29b Merge pull request #7158 from appwrite/fix-orfaned-project-deleteion
comparison fix
2023-11-16 16:43:14 -05:00
shimon e079d4a8b5 comparison fix 2023-11-16 23:42:03 +02:00
shimon 174449ce49 comparison fix 2023-11-16 23:14:26 +02:00
Torsten DittmannandGitHub 5a715ff68c Merge pull request #7149 from appwrite/1.4.x
1.4.x
2023-11-16 20:42:41 +01:00
Khushboo Verma 18994d308a Merge branch '1.4.x' into fix-error-logging 2023-11-17 00:20:41 +05:30
Khushboo Verma 881b92eeb8 Updated error codes in health tests 2023-11-17 00:19:43 +05:30
Christy JacobandGitHub 8013d79ae0 Merge pull request #7156 from appwrite/fix-orfaned-project-deleteion
comparison fix
2023-11-16 13:40:46 -05:00
shimon 8c9a662e14 comparison fix 2023-11-16 20:33:37 +02:00
shimon dceb1858b0 comparison fix 2023-11-16 20:31:53 +02:00
Khushboo Verma c34dc4dae0 Add flag to send logs to logger 2023-11-16 23:51:09 +05:30
Khushboo Verma fccbe405db Add chunkId to abuse key to prevent rate limit for SDKs 2023-11-16 23:04:38 +05:30
Jake Barnby 02b6659b35 SDK releases 2023-11-16 21:50:39 +13:00
Christy JacobandGitHub 7cce033682 Merge branch 'main' into 1.4.x 2023-11-15 11:13:53 -08:00
Christy JacobandGitHub 8e911dc83e Merge pull request #7150 from appwrite/chore-update-version
chore: update versions and changelog
2023-11-15 11:02:01 -08:00
德华 刘 e145ad4cb4 chore: update versions and changelog 2023-11-15 18:41:57 +00:00
Christy JacobandGitHub 6826a9efe0 Merge pull request #7124 from appwrite/usage-auto-skip-fix
wrapping create stats query
2023-11-15 10:38:47 -08:00
德华 刘 37e1e25170 chore: update versions and changelog 2023-11-15 18:31:33 +00:00
Christy JacobandGitHub 90ea00398b Merge pull request #7138 from appwrite/chore-update-database
Chore update database
2023-11-15 10:28:24 -08:00
Christy JacobandGitHub 5a06c015da Merge pull request #7148 from appwrite/chore-bump-console-3.2.7
Bump console to version 3.2.7
2023-11-15 10:28:03 -08:00
Christy JacobandGitHub e36dc817fb Merge branch '1.4.x' into usage-auto-skip-fix 2023-11-15 10:27:44 -08:00
Christy JacobandGitHub bc09b50c11 Merge pull request #7095 from appwrite/feat-delete-orphaned-projects
Add commit flag to delete orphaned projects task
2023-11-15 10:12:00 -08:00
shimon 6b5e734dfa indentation small fix 2023-11-15 20:09:50 +02:00
Steven Nguyen 31d5b75034 Bump console to version 3.2.7 2023-11-15 17:23:35 +00:00
德华 刘 7efecbca8b chore: fixed indentation 2023-11-15 17:20:50 +00:00
Torsten DittmannandGitHub 7766279025 Merge pull request #7041 from krishvsoni/kvs-patch
Update README-CN.md
2023-11-15 16:19:20 +01:00
Torsten Dittmann 42a92933a8 ci: remove install before composer validate 2023-11-15 15:42:44 +01:00
Torsten Dittmann df38a8efd1 ci: validate composer file and lock 2023-11-15 13:44:37 +01:00
Torsten DittmannandGitHub 115ca23a58 Merge pull request #5174 from yatharth1706/fix-4797-verified-user-getting-email-verifications
Fix verified user getting email verifications
2023-11-15 13:35:23 +01:00
Jake BarnbyandGitHub 74604a1a60 Merge pull request #7140 from appwrite/fix-delete-installation
Fix git installation deletion
2023-11-15 19:39:36 +13:00
Khushboo Verma 2e70ed59af Fix git installation deletion 2023-11-15 11:41:42 +05:30
Jake Barnby 94931ff4ce Fix non-PSR-compliant file name 2023-11-15 16:47:49 +13:00
Jake Barnby c3015340ce Update database 2023-11-15 16:46:43 +13:00
shimon 5a3fd99a28 remove deleteCollection()
and deleteDatabase() from deletes worker
2023-11-14 20:05:59 +02:00
Eldad A. FuxandGitHub f6408cd945 Merge pull request #7134 from appwrite/fix-recreate-repositories-documents
Fix patch script, make errors silent
2023-11-14 12:06:01 -05:00
Matej Bačo 1c4fea0fc3 Fix patch script, make errors silent 2023-11-14 17:45:02 +01:00
Eldad A. FuxandGitHub 29cd164448 Merge pull request #7133 from appwrite/fix-repositories-collection
Repositories recreation script
2023-11-14 11:09:38 -05:00
Matej Bačo 7e1b618769 Fix permission issues with repositories collection 2023-11-14 16:50:59 +01:00
shimon c2e5849c49 removing blank line 2023-11-14 16:49:45 +02:00
Khushboo Verma 141b864a56 Update permissions for create repository document 2023-11-14 19:49:23 +05:30
Matej Bačo 2572cb43c2 Code cleanup 2023-11-14 15:11:17 +01:00
Matej Bačo c133bccfa9 Finish recreate repos docs script 2023-11-14 15:06:58 +01:00
Matej Bačo fb6455b783 Add script to patch missing repos documents 2023-11-14 14:43:33 +01:00
Matej Bačo 4f2f76db22 Improve deletion relation with IDs 2023-11-14 14:11:54 +01:00
Torsten DittmannandGitHub 89c097eed3 Merge pull request #7131 from appwrite/fix-repository-deletion-bug
Only delete repositories linked to the particular project
2023-11-14 13:25:28 +01:00
Khushboo Verma 78eb5105e7 Update projectInternalId var 2023-11-14 17:08:09 +05:30
Khushboo Verma be85b173e7 Only delete repositories linked to the particular project 2023-11-14 16:49:16 +05:30
shimon 3d9ee8bc52 delete orphaned projects task 2023-11-13 19:28:48 +02:00
shimon 60c0f4c973 delete orphaned projects task 2023-11-13 19:27:20 +02:00
shimon dd0bf66212 delete orphaned projects task 2023-11-13 19:24:55 +02:00
Eldad A. FuxandGitHub be6b31b43f Merge pull request #7123 from appwrite/feat-health-tresholds
Implement health tresholds
2023-11-13 08:57:51 -05:00
shimon b9007a5956 wrapping create stats query
with Authorization::skip
2023-11-13 15:41:10 +02:00
Matej Bačo 967854d34a Fix grammar 2023-11-13 14:39:33 +01:00
Matej Bačo a029287e5a Implement health tresholds 2023-11-13 14:07:11 +01:00
Jake BarnbyandGitHub 5871178fc0 Merge pull request #7106 from appwrite/1.4.x
1.4.x
2023-11-13 16:45:23 +13:00
Jake BarnbyandGitHub 573a035f92 Merge pull request #7112 from appwrite/chore-update-version
Update version + cache buster
2023-11-10 13:46:11 +13:00
Jake Barnby 97757c9984 Update readmes + migrations 2023-11-10 13:25:28 +13:00
Jake Barnby 4d54e7bb36 Update version + cache buster 2023-11-10 13:09:00 +13:00
Jake BarnbyandGitHub 2d9ac9bac1 Merge pull request #7111 from appwrite/chore-update-db
Chore update db
2023-11-10 12:42:53 +13:00
Jake Barnby 17919ecfbe Update database 2023-11-10 12:11:21 +13:00
Christy JacobandGitHub 7e8778c185 Merge pull request #7104 from appwrite/chore-bump-dependencies
Chore bump dependencies
2023-11-09 08:20:36 -08:00
德华 刘 f1ea4f761e chore: fix markdown syntax 2023-11-09 16:08:20 +00:00
德华 刘 c014e40c54 chore: update changelog 2023-11-09 16:05:33 +00:00
德华 刘 f70b418c5c chore: updates for appwrite 1.4.10 2023-11-09 15:57:37 +00:00
Christy JacobandGitHub e2fa5356bc Merge branch '1.4.x' into chore-bump-dependencies 2023-11-09 07:55:12 -08:00
Christy JacobandGitHub acc49eb998 Merge pull request #7092 from appwrite/fix-null-password-history
Handle cases where password history could contain NULLs
2023-11-09 07:53:02 -08:00
Christy JacobandGitHub d33b8c2ce1 Merge branch '1.4.x' into fix-null-password-history 2023-11-09 07:44:44 -08:00
shimon 91fed7a255 editing the response models 2023-11-09 12:54:33 +02:00
Bradley SchofieldandGitHub 56310bc4fa Merge branch 'main' into fix-null-password-history 2023-11-09 10:41:17 +00:00
Steven Nguyen 5087ae61e7 Bump utopia-php/vcs and utopia-php/messaging 2023-11-09 05:51:47 +00:00
Steven NguyenandGitHub 0c264a107a Merge branch '1.4.x' into fix-4797-verified-user-getting-email-verifications 2023-11-08 17:37:43 -08:00
Christy JacobandGitHub fcf9e9c4d0 Merge pull request #7066 from appwrite/fix-vcs-documents-not-deleted
Delete linked VCS repos and comments
2023-11-08 14:28:45 -08:00
德华 刘 2bf247053d chore: address review comments 2023-11-08 21:59:29 +00:00
Eldad A. FuxandGitHub d64d56069f Merge pull request #6828 from appwrite/feat-improve-migration-validations
Improve Error Handling and Add more Validation
2023-11-08 16:03:04 -05:00
shimon d54615d376 project.php 2023-11-08 18:12:36 +02:00
Christy JacobandGitHub 780dffd973 Merge pull request #7056 from appwrite/fix-prevent-webhook-abuse
Added cURL timeout and not store response data for webhooks
2023-11-08 07:35:38 -08:00
Eldad A. FuxandGitHub 2baadb6e95 Merge pull request #7096 from appwrite/refactor-db-health
Refactor db health api
2023-11-08 09:54:10 -05:00
Bradley Schofield 54e5985940 Fix wrong operator for password history
This fixes a potential chance for nulls to be introduced into the password history array when $password is null and $passwordHistory != 0
2023-11-08 14:35:16 +00:00
shimon dd9d8010c1 db health api throws an error when ping fails 2023-11-08 15:20:50 +02:00
shimon bae9da5695 Merge remote-tracking branch 'origin/main' 2023-11-08 15:13:53 +02:00
Khushboo Verma 05ab73ee37 Update download limit to 5 MB 2023-11-08 17:12:07 +05:30
Khushboo Verma e461a4f14b Added max download limit of 1 GB 2023-11-08 16:09:17 +05:30
shimon 0f3c438976 added inf metric 2023-11-08 12:05:51 +02:00
shimon ad63a377ab added inf metric 2023-11-08 11:09:32 +02:00
Khushboo Verma 365c520267 Fixed linter error 2023-11-08 12:41:16 +05:30
Khushboo Verma 698bce2c36 Limit max redirects to 5 2023-11-08 12:32:48 +05:30
Khushboo Verma 93d3cb7bcf Merge branch '1.4.x' into fix-prevent-webhook-abuse 2023-11-08 11:30:00 +05:30
Christy JacobandGitHub fd7b354113 Merge pull request #7084 from appwrite/fix-proxy-create-rule-500
fix: proxy create rule 500
2023-11-07 10:49:52 -08:00
Christy JacobandGitHub 8feb01d857 Merge pull request #7085 from appwrite/fix-missing-sessionId-error
fix: missing sessionId error
2023-11-07 10:43:05 -08:00
Christy JacobandGitHub 888c7bf39c Merge pull request #7091 from appwrite/fix-missing-functionId-error
fix: missing functionId error on create execution
2023-11-07 10:41:55 -08:00
Christy JacobandGitHub 2d2400fdff Merge branch '1.4.x' into fix-missing-sessionId-error 2023-11-07 10:27:46 -08:00
Christy JacobandGitHub c079d2f978 Merge branch '1.4.x' into fix-missing-functionId-error 2023-11-07 10:27:37 -08:00
Christy JacobandGitHub f4cdd0accc Merge branch '1.4.x' into fix-proxy-create-rule-500 2023-11-07 10:27:31 -08:00
Christy JacobandGitHub 8dff9b9b49 Merge pull request #7093 from appwrite/sync-1.4.x-main
Sync 1.4.x main
2023-11-07 10:26:04 -08:00
德华 刘 9acc23049c Merge branch 'main' of https://github.com/appwrite/appwrite into sync-1.4.x-main 2023-11-07 18:12:58 +00:00
Christy JacobandGitHub f53fbc19d4 Merge branch '1.4.x' into fix-missing-functionId-error 2023-11-07 10:10:03 -08:00
Bradley Schofield d5d7440a68 Merge branch 'main' into feat-improve-migration-validations 2023-11-07 16:21:35 +00:00
Bradley Schofield 44cbfb2769 Add null check to hash 2023-11-07 16:17:14 +00:00
Christy JacobandGitHub cb512710f2 Merge pull request #7087 from appwrite/fix-4960-disabled-usage
Ensure usage endpoints don't throw 500 when usage is disabled
2023-11-07 07:34:06 -08:00
Khushboo Verma e4191f206d Merge branch '1.4.x' into fix-prevent-webhook-abuse 2023-11-07 17:34:10 +05:30
loks0n 7e37c973ce fix: missing functionId error on create execution 2023-11-07 10:25:33 +00:00
Eldad A. FuxandGitHub 8db73a3431 Merge pull request #7078 from appwrite/fix-optional-services
Fix optional services
2023-11-07 05:14:52 -05:00
Khushboo Verma 018800c7ae Merge branch '1.4.x' into fix-prevent-webhook-abuse 2023-11-07 13:02:25 +05:30
Steven Nguyen 55a73efeab Ensure usage endpoints don't throw 500 when usage is disabled 2023-11-06 21:28:45 +00:00
loks0n dfc8c919d4 fix: missing sessionId error 2023-11-06 15:06:56 +00:00
loks0n 75669b9b2e chore: better error msg 2023-11-06 14:49:33 +00:00
loks0n 4440859c05 chore: fmt 2023-11-06 14:48:54 +00:00
loks0n 61c5f371b7 chore: use base Exception 2023-11-06 14:45:56 +00:00
loks0n 8b24c44fb9 fix: proxy create rule 500 2023-11-06 14:44:00 +00:00
Jake Barnby 2292143750 Add service/all test 2023-11-06 12:34:44 +13:00
Jake Barnby 1429c82aa0 Make VCS, proxy and migrations services non-optional so they aren't toggled with enable/disable all service routes 2023-11-06 12:04:56 +13:00
KRISH SONIandGitHub e4bde92b35 Merge branch 'main' into kvs-patch 2023-11-05 20:38:36 +05:30
shimon f64d0ab8a8 Merge branch '1.4.x' of github.com:appwrite/appwrite into refactor-usage-sn
 Conflicts:
	.gitmodules
	CHANGES.md
	Dockerfile
	composer.lock
	src/Appwrite/Migration/Migration.php
2023-11-05 17:03:13 +02:00
Yatharth Verma 0ecdeed329 Checkout to latest tag of console submodule 2023-11-04 13:01:05 +05:30
Christy JacobandGitHub 19e5235491 Merge pull request #7073 from appwrite/fix-health-queue-size
Fix the Health service's get X queue endpoints
2023-11-04 00:14:54 +04:00
Christy JacobandGitHub bcdac2f803 Merge pull request #7074 from appwrite/fix-delete-schedules
Fix regression from worker refactor
2023-11-04 00:11:56 +04:00
Steven Nguyen 30b8ff9166 Add pagination to listByGroup to fix infinite loop problem 2023-11-03 17:47:52 +00:00
Steven Nguyen 03da0380ea Ensure schedules for deleted projects are deleted 2023-11-03 17:47:16 +00:00
Steven Nguyen 9f7bbe4869 Fix the Health service's get X queue endpoints
Before this, the endpoints called $client->sumProcessingJobs() which
only gets the currently processing jobs, but this endpoint should return
what's currently in queue, pending to be processed. This should be
retrieved using $client->getQueueSize().
2023-11-03 16:57:30 +00:00
Christy JacobandGitHub 9fc7c1384a Merge pull request #7071 from appwrite/fix-swoole-warning
feat: fix swoole warning
2023-11-03 19:46:59 +04:00
loks0n 305c781206 feat: fix swoole warning 2023-11-03 12:18:24 +00:00
Khushboo Verma 4a3b8fb70e Change projectId to projectInternalId 2023-11-03 15:32:50 +05:30
Khushboo Verma 9be32184d6 Refactor loop 2023-11-03 14:54:51 +05:30
Christy JacobandGitHub 8e1afa11f9 Merge pull request #7070 from appwrite/feat-remove-mock-endpoints
Feat remove mock endpoints
2023-11-02 21:38:15 +04:00
Bradley Schofield 23631f8282 Update specs 2023-11-02 17:12:08 +00:00
Bradley Schofield d921c03ee1 Update mock.php 2023-11-02 17:07:16 +00:00
Bradley Schofield b15dac16c8 Revert "Remove Mock API"
This reverts commit 2d045335ff.
2023-11-02 17:05:51 +00:00
Bradley Schofield 2d045335ff Remove Mock API 2023-11-02 17:05:31 +00:00
Bradley Schofield e158d134b0 Merge branch 'main' into feat-improve-migration-validations 2023-11-02 10:48:38 +00:00
shimon 1c1028412a refactor users, project, functions usage display 2023-11-01 20:44:06 +02:00
KRISH SONIandGitHub 49e23e8916 Merge branch 'main' into kvs-patch 2023-11-01 21:05:14 +05:30
shimon 04290730b2 refactor storage, databases usage display 2023-11-01 17:20:12 +02:00
Khushboo Verma d95e4790a0 Delete VCS documents when project is deleted 2023-11-01 15:06:28 +05:30
Khushboo Verma 8798d183bd Merge branch '1.4.x' into fix-vcs-documents-not-deleted 2023-11-01 14:41:07 +05:30
Khushboo Verma 64630490a5 Delete linked VCS repos and comments on function deletion 2023-11-01 14:36:31 +05:30
Christy JacobandGitHub 281b50ba4a Merge pull request #7063 from appwrite/1.4.x
1.4.x
2023-11-01 12:32:11 +04:00
Jake Barnby 3652ab61d8 Revert "Enum tests"
This reverts commit 74450b1178.
2023-11-01 21:02:42 +13:00
Jake Barnby 74450b1178 Enum tests 2023-11-01 18:29:54 +13:00
Christy JacobandGitHub 7110836beb Merge pull request #7061 from appwrite/feat-1.4.9-release-prep
Prepare 1.4.9 release
2023-10-31 23:59:11 +04:00
Steven Nguyen 358fdb6878 Add 1.4.9 to CHANGES.md 2023-10-31 12:43:52 -07:00
Steven Nguyen 755699de8a Bump appwrite version to 1.4.9 2023-10-31 12:40:45 -07:00
Christy JacobandGitHub 311fc0f0c4 Merge pull request #7059 from appwrite/fix/function-domains-execution
Fix 400 error on function domain execution
2023-10-31 23:17:16 +04:00
Steven NguyenandGitHub 7c8a9ad4d2 Fix incorrect general_protocol_unsupported error
$requestHeaders was being referenced before it was set. This PR reverts
code back to using $swooleRequest to extract the header.
2023-10-31 18:31:36 +00:00
Steven NguyenandGitHub c4e22b1085 Fix warning when cookie is null 2023-10-31 18:25:35 +00:00
Khushboo Verma ce6d873cf4 Added cURL timeout and not store response data for webhooks 2023-10-31 15:55:11 +05:30
KRISH SONIandGitHub 1809140659 Merge branch 'main' into kvs-patch 2023-10-31 15:12:48 +05:30
Christy JacobandGitHub 9fded16d3f Merge pull request #7053 from appwrite/1.4.x
Sync main with 1.4.x
2023-10-31 12:27:08 +04:00
KRISH SONIandGitHub 42288f3070 Merge branch 'main' into kvs-patch 2023-10-30 21:08:42 +05:30
Christy JacobandGitHub 0a6ee966a2 Merge pull request #7045 from appwrite/update-project-creation-logic
chore: update project creation database allocation
2023-10-30 19:34:21 +04:00
德华 刘 744f2fbc1c chore: update project creation database allocation 2023-10-30 14:46:54 +00:00
KRISH SONIandGitHub e8110953b5 Merge branch 'main' into kvs-patch 2023-10-29 23:22:21 +05:30
KRISH SONIandGitHub 68486d4795 Update README-CN.md 2023-10-29 23:17:27 +05:30
Christy JacobandGitHub fbb82ceb81 Merge pull request #7028 from appwrite/feat-1.4.8-release-prep
Prepare 1.4.8 release
2023-10-27 23:32:00 +04:00
Steven Nguyen 4eab2a01f7 Add 1.4.8 to CHANGES.md 2023-10-27 10:31:43 -07:00
Steven Nguyen c1df289df7 Bump appwrite version to 1.4.8 2023-10-27 10:31:43 -07:00
Christy JacobandGitHub 255659f009 Merge pull request #7027 from appwrite/chore-bump-console
Bump console to version 3.2.5
2023-10-27 21:01:29 +04:00
Steven Nguyen c3b7b5af80 Fix inconsistent composer.lock
For some reason the composer.json had version 0.2.* of
utopia-php/messaging, but the composer.lock had 0.1.1.
2023-10-27 09:45:07 -07:00
Steven Nguyen 3abb74c937 Bump console to version 3.2.5 2023-10-27 09:33:21 -07:00
Christy JacobandGitHub 24824e1582 Merge pull request #7015 from appwrite/feat-delete-orphaned-projects
Feat delete orphaned projects
2023-10-27 20:21:52 +04:00
Christy JacobandGitHub 106f4cfc45 Merge pull request #6487 from appwrite/fix-unique-in-errors
Updated error message to not user magic strings
2023-10-27 20:13:16 +04:00
shimon 75484fa018 $collectionsConfig fix 2023-10-27 19:07:27 +03:00
shimon 917a4e9ab1 $collectionsConfig fix 2023-10-27 19:06:16 +03:00
Christy JacobandGitHub bcdebee598 Merge pull request #7024 from appwrite/fix-cookie-header
Fix: Cookie Header
2023-10-27 19:36:05 +04:00
Matej Bačo 9a6d5f8101 Improve var name 2023-10-27 17:25:19 +02:00
Matej Bačo 71807635b3 Move cookie-header logic to request 2023-10-27 17:23:43 +02:00
shimon 07ef5535ef dry run 2023-10-27 18:23:41 +03:00
Christy JacobandGitHub e17bb94e39 Merge pull request #6495 from appwrite/fix-mails
Fix certificate emails and add support for variables in email template subject
2023-10-27 19:17:50 +04:00
Christy JacobandGitHub 21220a2051 Merge pull request #6894 from appwrite/refactor-vcs-tests
Add VCS tests
2023-10-27 19:04:44 +04:00
德华 刘 6d237853e6 chore: revert console 2023-10-27 14:38:09 +00:00
Khushboo Verma 9164ed1045 Fixed linter errors 2023-10-27 19:43:07 +05:30
Khushboo Verma 30abfb01b7 Composer update 2023-10-27 19:40:24 +05:30
Khushboo Verma cba20d8b96 Addressed PR comments 2023-10-27 19:38:33 +05:30
Matej Bačo b2d385944a Add function domain cookie test 2023-10-27 15:33:26 +02:00
Khushboo Verma bb7278dd12 Merge branch 'refactor-vcs-tests' of github.com:appwrite/appwrite into refactor-vcs-tests 2023-10-27 18:14:47 +05:30
Khushboo Verma 36fcc6c293 Resolve merge conflicts 2023-10-27 18:13:50 +05:30
Matej Bačo 0a1a2af218 Connect VCS base to VCS test 2023-10-27 12:57:26 +02:00
Matej Bačo 84559b8f56 Improve cookie test 2023-10-27 12:01:37 +02:00
Christy JacobandGitHub 3700751592 Merge pull request #7016 from appwrite/main
Sync 1.4.x with main
2023-10-27 13:35:54 +04:00
德华 刘 2ee505b3f8 chore: update composer 2023-10-27 09:35:36 +00:00
Matej Bačo 740d9def8e Linter fix 2023-10-27 10:33:08 +02:00
Matej Bačo 94ff3baa9c Fix cookie headers 2023-10-27 10:26:46 +02:00
Steven Nguyen 7c1ab91ebc Add support for variables in email template subject 2023-10-26 16:58:31 -07:00
Steven Nguyen fc4fa977a0 Update emails from certificate worker to match pattern of other emails
Since the certificate worker is sending a job to the mails worker just
like the controllers, it should prepare the email in the same way by
localizing and then sending the inner template for the mail worker to
apply the layout and final variables.

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

It is the responsibility of the controller to perform localization and
determine whether a custom or default email template should be used.
If the default one is used, it has to be rendered using the hello,
footer, thanks, and signature variables so that it matches the custom
template. Then, it can be sent to the worker.
2023-10-26 16:47:14 -07:00
Steven Nguyen 3206cee36f Leave user placeholder in default email template
This matches up with the default template and gives the developer an
idea about the available variables and how to use them.
2023-10-26 16:23:58 -07:00
Steven Nguyen 70a3820009 Remove unused mail vars 2023-10-26 16:23:58 -07:00
Christy JacobandGitHub ab760ba46b Merge pull request #7017 from appwrite/chore-remove-install-analytics
Remove install analytics
2023-10-27 01:09:51 +04:00
Christy JacobandGitHub 3caa49a547 Merge pull request #6938 from appwrite/fix-gitpod-composer-install
gitpod: fix composer usage
2023-10-27 00:39:32 +04:00
Torsten Dittmann c81b4576b4 chore: remove install analytics 2023-10-26 22:25:35 +02:00
Christy JacobandGitHub 4becf5138d Merge pull request #7011 from krishvsoni/kvs-patch
added docs url
2023-10-27 00:25:17 +04:00
Christy JacobandGitHub 16fe42bca9 Merge pull request #6980 from appwrite/feat-support-selfdb
Fix database override
2023-10-27 00:21:59 +04:00
Christy JacobandGitHub d16e3f2f56 Merge branch '1.4.x' into feat-support-selfdb 2023-10-27 00:19:02 +04:00
shimon 45a0954b24 dry run 2023-10-26 21:12:43 +03:00
shimon fc348b2055 dry run 2023-10-26 21:12:13 +03:00
shimon 7e6378b23b dry run 2023-10-26 20:56:32 +03:00
shimon 0f43aa6872 dry run 2023-10-26 20:53:36 +03:00
Christy JacobandGitHub 2b939a57d0 Merge pull request #6198 from appwrite/chhore-parallelize-tests
chore: test parallel executions
2023-10-26 21:28:43 +04:00
德华 刘 c8788e2a13 chore: update CI 2023-10-26 16:43:50 +00:00
德华 刘 09912592cf chore: update CI 2023-10-26 16:27:57 +00:00
德华 刘 c9c688c592 chore: update CI 2023-10-26 16:26:13 +00:00
德华 刘 5d634384c8 chore: update CI 2023-10-26 16:24:14 +00:00
德华 刘 a4c59b838a chore: remove commented files 2023-10-26 16:15:52 +00:00
Matej Bačo c3a80dcebc Fix GraphQL function test 2023-10-26 16:50:35 +02:00
shimon 831122679f creating script 2023-10-26 17:45:23 +03:00
Christy JacobandGitHub 1d9b94ee4d Merge branch 'main' into chhore-parallelize-tests 2023-10-26 14:31:21 +04:00
Jake BarnbyandGitHub 8628e94d0c Merge pull request #7013 from appwrite/chore-updates-deps
Update database to include listCollections fix
2023-10-26 22:49:55 +13:00
Jake Barnby 89236955fb Fix setting metadata 2023-10-26 22:28:23 +13:00
Jake Barnby 995c404f35 Update db 2023-10-26 21:24:30 +13:00
Jake Barnby 2d897889c9 Merge remote-tracking branch 'origin/main' into chore-updates-deps 2023-10-26 21:22:41 +13:00
KRISH SONIandGitHub 822596b2c9 Merge branch 'main' into kvs-patch 2023-10-25 23:56:54 +05:30
KRISH SONIandGitHub 191235ecaf added docs url 2023-10-25 23:24:26 +05:30
Torsten DittmannandGitHub 2910e87c7a Merge pull request #7009 from appwrite/fix-worker-permissions-reset
fix: worker database authorization reset
2023-10-25 19:51:20 +02:00
Torsten Dittmann 404ad8653d fix: worker permissions reset 2023-10-25 19:23:55 +02:00
shimon 6527a506af composer json 2023-10-25 17:27:15 +03:00
shimon a42577b1aa composer json 2023-10-25 17:14:10 +03:00
shimon 5e17cde10f usage test 2023-10-25 15:57:03 +03:00
shimon 19fe1c3fa5 migration ver 2023-10-25 15:08:01 +03:00
shimon 4c6976488c clean up 2023-10-25 15:06:54 +03:00
Christy JacobandGitHub fa0d9139c1 Merge pull request #7007 from appwrite/chore-bump-console
chore: bump console version
2023-10-25 15:36:34 +04:00
Torsten Dittmann f9a3ae1421 chore: bump console version 2023-10-25 13:35:14 +02:00
Christy JacobandGitHub db52e047c6 Merge pull request #7006 from appwrite/fix-http-status-code
Updated the error code for unsupported protocol
2023-10-25 14:56:45 +04:00
Khushboo Verma 17de0d1ea7 Updated the error code for unsupported protocol 2023-10-25 15:55:46 +05:30
shimon b775c53666 clean up 2023-10-25 13:16:25 +03:00
shimon c11b3ff522 workers start hook 2023-10-25 12:46:45 +03:00
shimon 14ac780180 workers start hook 2023-10-25 12:10:30 +03:00
shimon ef347f65b8 usage workers 2023-10-25 11:41:42 +03:00
shimon b7675df9b9 usage workers 2023-10-25 11:38:44 +03:00
shimon 8736a01814 CHANGES.md
migrations::V20
2023-10-25 11:25:31 +03:00
shimon b80bfb0a3c usage sync 2023-10-25 11:00:21 +03:00
shimon 9fad9dca88 usage sync 2023-10-25 10:39:59 +03:00
Jake Barnby 5732171a2a Merge remote-tracking branch 'origin/1.4.x' into feat-query-metadata
# Conflicts:
#	app/init.php
#	composer.json
#	composer.lock
2023-10-25 18:21:16 +13:00
Jake BarnbyandGitHub 7d10174193 Merge pull request #7002 from appwrite/chore-updates-deps
Update dependencies
2023-10-25 14:32:08 +13:00
Jake Barnby 8db372a7d3 Update dependencies 2023-10-25 13:55:32 +13:00
Torsten DittmannandGitHub 5069aef6ac Merge pull request #6998 from appwrite/feat-realtime-error-handling
feat: realtime error handling
2023-10-24 16:12:21 +02:00
Torsten Dittmann fbcbd0d430 feat: realtime error handling 2023-10-24 14:32:22 +02:00
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
Christy JacobandGitHub 3a53dce467 Merge pull request #6994 from appwrite/1.4.x
Update main with 1.4.x
2023-10-24 01:11:13 +04:00
Christy JacobandGitHub 3e2b6c4344 Merge pull request #6990 from appwrite/feat-1.4.7-release-prep
Prepare 1.4.7 release
2023-10-24 00:49:44 +04:00
Steven Nguyen 84ea81f56d Add 1.4.7 to CHANGES.md 2023-10-23 13:36:29 -07:00
Steven Nguyen 3addf76a2a Bump appwrite version to 1.4.7 2023-10-23 13:36:29 -07:00
Christy JacobandGitHub ce8c7e1527 Merge pull request #6988 from appwrite/fix-functions-worker
Fix missing body in async function execution
2023-10-23 23:40:58 +04:00
Torsten DittmannandGitHub 342deb4989 Merge pull request #6465 from appwrite/fix-docs-links
Fix docs links CONTRIBUTING.md
2023-10-23 19:24:43 +02:00
shimon 0411f9f530 fix function-worker body param typo 2023-10-23 16:33:26 +03:00
Khushboo Verma ad4db114bc Updated composer.lock 2023-10-23 15:23:52 +05:30
Khushboo Verma c0460289b7 Remove unused value from docker-compose 2023-10-23 15:19:23 +05:30
Khushboo Verma a09c4281c8 Revert "Remove unused value from docker-compose"
This reverts commit c7435e7e78.
2023-10-23 15:18:48 +05:30
Khushboo Verma c7435e7e78 Remove unused value from docker-compose 2023-10-23 15:16:42 +05:30
Khushboo Verma 2cf8639b68 Resolve merge conflicts 2023-10-23 15:06:01 +05:30
Khushboo Verma 97c1099961 Refactor code and add more assertions 2023-10-23 15:03:24 +05:30
shimon fc34d2ff7e Merge remote-tracking branch 'origin/main' 2023-10-23 11:47:30 +03:00
Christy JacobandGitHub 45c44bb51c Merge pull request #6986 from appwrite/fix-db-mod
fix mod condition
2023-10-23 12:41:05 +04:00
Christy JacobandGitHub 14f35589b3 Merge branch 'main' into fix-db-mod 2023-10-23 12:40:05 +04:00
德华 刘 3dee8d6ecd fix: update incorrect method call 2023-10-23 08:34:57 +00:00
808f682fa7 Update app/controllers/api/projects.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2023-10-23 11:25:31 +03:00
shimon 2ef8490691 fix mod condition 2023-10-23 11:22:28 +03:00
shimon b09a254d3c fix mod condition 2023-10-23 10:46:15 +03:00
shimon 76e2b39c1a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	app/controllers/api/projects.php
2023-10-23 01:08:04 +03:00
Christy JacobandGitHub e2eccaca8b Merge pull request #6985 from appwrite/fix-db-mod
Fix db mod
2023-10-23 02:07:15 +04:00
shimon a841734d50 fix mod condition 2023-10-23 01:05:10 +03:00
Christy JacobandGitHub 765391cd7c Merge branch 'main' into feat-support-selfdb 2023-10-23 01:56:54 +04:00
shimon ac231914d4 fix mod condition 2023-10-23 00:55:57 +03:00
shimon 453ce03881 fix mod condition 2023-10-23 00:47:48 +03:00
Christy JacobandGitHub d1deef43c5 Merge pull request #6984 from appwrite/fix-db-mod
fix mod condition
2023-10-23 01:11:05 +04:00
shimon 0d5127d90b fix mod condition 2023-10-23 00:08:55 +03:00
Christy JacobandGitHub bb46e3869c Merge pull request #6851 from appwrite/feat-sm-db
update projects database
2023-10-22 22:35:54 +04:00
德华 刘 00d2a5ffcd chore: update create projects endpoints 2023-10-22 18:33:15 +00:00
德华 刘 79612bd80f Merge branch 'main' of https://github.com/appwrite/appwrite into feat-sm-db 2023-10-22 18:30:14 +00:00
德华 刘 c0f099e176 chore: update test file 2023-10-22 15:28:39 +00:00
德华 刘 42ce9d881a Merge branch 'main' of https://github.com/appwrite/appwrite into chhore-parallelize-tests 2023-10-22 15:16:40 +00:00
Matej Bačo 1787e57448 Fix database override 2023-10-22 16:18:47 +02:00
Christy JacobandGitHub fc35e0f6ff Merge pull request #6954 from appwrite/1.4.x
Merge 1.4.x to main
2023-10-20 17:36:56 +04:00
德华 刘 b0b9bd3268 Merge branch '1.4.x' of https://github.com/appwrite/appwrite into chhore-parallelize-tests 2023-10-19 17:10:15 +00:00
德华 刘 9f3561aaa3 chore: cleanup ci file 2023-10-19 17:09:49 +00:00
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
Christy Jacob 6141d02e16 Merge branch 'refactor-workers-sn' of https://github.com/appwrite/appwrite into chhore-parallelize-tests 2023-10-18 21:49:43 +00:00
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
Vincent (Wen Yu) Ge 175879a352 remove package lock 2023-10-18 19:25:00 +00:00
Vincent (Wen Yu) Ge 397bdd50f4 Try even older commit for package-lock.json 2023-10-18 19:24:11 +00:00
Pratik GuptaandGitHub 6c4983f65f Update dart EXAMPLES.md 2023-10-19 00:52:39 +05:30
Vincent (Wen Yu) Ge 413209d990 Fix checking out the wrong commit to revert package.json 2023-10-18 19:21:28 +00:00
Pratik GuptaandGitHub ea282a1761 Update dart GETTING_STARTED.md 2023-10-19 00:51:10 +05:30
Vincent (Wen Yu) Ge 3a80d58353 Revert package json changes 2023-10-18 19:14:57 +00:00
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
Khushboo Verma eac911a006 Remove VCS repo id from env variables 2023-10-18 13:27:29 +05:30
Khushboo Verma 979054e5ca Merge branch 'main' into refactor-vcs-tests 2023-10-18 13:25:40 +05:30
Khushboo Verma 2914a4d1c5 Add test for createRepo endpoint 2023-10-18 13:24:32 +05:30
Jake Barnby bb0744842d Set metadata for all database instantiations 2023-10-18 16:21:10 +13:00
Steven NguyenandGitHub 9fbf10f9f5 Merge branch 'main' into fix-docs-links 2023-10-17 13:08:03 -07:00
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
Torsten DittmannandGitHub 80574838a4 gitpod: fix composer usage 2023-10-17 15:19:29 +02:00
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
Jake Barnby 64d582a43a Update for package changes 2023-10-17 21:31:00 +13:00
Jake Barnby 62ce68f1b5 Update packages 2023-10-17 16:48:26 +13:00
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
Bradley Schofield 23a1da58fb Update Status Codes 2023-10-16 11:57:17 +01:00
Yatharth Verma 1489f77499 fix changes suggested by steven and also fixed some bugs came after merging with 1.4.x 2023-10-14 12:45:29 +05:30
Yatharth Verma 96f389cee5 merge 1.4.x changes to my branch 2023-10-14 11:57:40 +05:30
Yatharth Verma b420c570bc Reverted to last commit before merging main 2023-10-14 11:51:27 +05:30
Steven NguyenandGitHub 5956c4c247 Merge branch 'main' into fix-unique-in-errors 2023-10-13 12:03:23 -07:00
Bradley Schofield 5274f1234b Merge branch 'main' into feat-improve-migration-validations 2023-10-13 16:23:25 +01:00
Bradley Schofield d4b53e5014 Run Linter 2023-10-13 16:23:20 +01:00
Khushboo Verma fe7aa60a11 Updated composer.json 2023-10-13 18:55:33 +05:30
Khushboo Verma f18d33a0d6 Resolved merge conflicts 2023-10-13 18:54:14 +05:30
Khushboo Verma 66ec7f2e1d VCS tests 2023-10-13 18:50:58 +05:30
Khushboo Verma 615bd5148e Test for create and update function using VCS 2023-10-13 03:11:31 +05:30
Khushboo Verma bb75036efe WIP: Mocked GitHub installation 2023-10-13 01:45:27 +05:30
Vincent (Wen Yu) GeandGitHub eaba2eb7e5 Merge branch 'main' into doc-sdks-python-update-getting-started-guide 2023-10-12 11:00:06 -04:00
Vincent (Wen Yu) Ge 291654bee9 Rename ID -> Keys for attribute and index for consistency 2023-10-11 19:29:04 +00:00
Pratik GuptaandGitHub 9ccaa5a883 Update GETTING_STARTED.md
Changed user1 name and (email1 -> email).
2023-10-11 18:14:12 +05:30
Khushboo Verma dbd6dbcb5b WIP: Progress with VCS tests 2023-10-11 02:56:15 +05:30
Khushboo Verma 1f97bb39b3 WIP: VCS tests 2023-10-11 01:31:22 +05:30
Khushboo Verma bd2999037d WIP: Add VCS tests 2023-10-09 15:15:36 +05:30
shimon 31466c44d8 update projects database 2023-10-08 16:44:14 +03:00
Bradley Schofield 9cb1cf418c Improve Error Handling and Add more Validation 2023-10-06 17:02:01 +01:00
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
Vincent (Wen Yu) Ge e5c828fbbd Attribute keys must be unique and can't use unique() 2023-10-04 13:56:18 +00:00
Vincent (Wen Yu) Ge c12999dc74 Remove extra ` tags 2023-10-04 13:52:19 +00:00
Vincent (Wen Yu) Ge a2c9c8b82e Updated error message to not user magic strings 2023-10-04 13:49:45 +00:00
Vincent (Wen Yu) GeandGitHub 9b0a2fdc73 Fix docs links CONTRIBUTING.md 2023-10-03 15:10:09 -04:00
Yatharth VermaandYatharth Verma 5771ad35ed fix test cases 2023-09-22 22:56:58 +05:30
Yatharth VermaandYatharth Verma 56a3b3df99 Add test cases 2023-09-22 22:56:58 +05:30
Yatharth Verma 6b47999120 fix conflicts 2023-09-22 22:56:07 +05:30
Yatharth Verma f07f246f8d fix conflicts 2023-09-22 22:53:41 +05:30
Christy Jacob ab5fa96668 chore: update ci file 2023-09-09 02:39:51 +00:00
Christy Jacob 8a28c9ddbc chore: update ci file 2023-09-09 02:29:50 +00:00
Christy Jacob 85fcddcead chore: update ci file 2023-09-09 02:20:25 +00:00
Christy Jacob ff4816a436 chore: update ci file 2023-09-09 02:17:21 +00:00
Christy Jacob 1c9dfb903d chore: update ci file 2023-09-09 01:58:37 +00:00
Christy Jacob 238089409a chore: update ci file 2023-09-09 01:39:01 +00:00
Christy Jacob e70d5beb0d chore: update ci file 2023-09-09 01:29:56 +00:00
Christy Jacob e390cf0d70 chore: update CI file 2023-09-09 00:11:14 +00:00
Christy Jacob 29e056c71b chore: update CI file 2023-09-09 00:00:06 +00:00
Christy Jacob 0a2874458b chore: update CI file 2023-09-08 23:54:27 +00:00
Christy Jacob 0ddf036bd5 chore: update CI file 2023-09-08 23:44:19 +00:00
Christy Jacob 29d0820b10 chore: update CI file 2023-09-08 23:41:16 +00:00
Christy Jacob 889942ee76 chore: update CI file 2023-09-08 23:37:41 +00:00
Christy Jacob 01aaa6b337 chore: update CI file 2023-09-08 23:34:23 +00:00
Christy Jacob eaf94aa670 chore: update CI file 2023-09-08 23:31:04 +00:00
Christy Jacob 1a93aa4085 chore: update CI file 2023-09-08 23:24:16 +00:00
Christy Jacob 0f67c1151f chore: update CI file 2023-09-08 23:18:26 +00:00
Christy Jacob 10f6cc7b0b chore: update CI file 2023-09-08 23:12:11 +00:00
Christy Jacob 733b7ed612 chore: update CI file 2023-09-08 22:57:25 +00:00
Christy Jacob 1415727f10 chore: update CI file 2023-09-08 22:53:50 +00:00
Christy Jacob ebb4bc7e6b chore: update CI file 2023-09-08 22:39:11 +00:00
Christy Jacob 21357bfe7b chore: update CI file 2023-09-08 22:32:54 +00:00
Christy Jacob 5cb14fbec5 feat: test upload artifact 2023-09-08 22:11:40 +00:00
Christy Jacob 5793a11480 feat: test upload artifact 2023-09-08 22:10:56 +00:00
Christy Jacob 2ed6327c1e feat: test upload artifact 2023-09-08 22:03:32 +00:00
Christy Jacob cf030457e0 feat: test upload artifact 2023-09-08 21:55:28 +00:00
Christy Jacob 27f77759ed feat: test upload artifact 2023-09-08 21:48:31 +00:00
Christy Jacob dec4f4722b feat: test upload artifact 2023-09-08 21:43:18 +00:00
Christy Jacob 8bd822d171 feat: test upload artifact 2023-09-08 21:35:01 +00:00
Christy Jacob 13c6666883 feat: test upload artifact 2023-09-08 21:29:11 +00:00
Christy Jacob fa99c4584e feat: test upload artifact 2023-09-08 20:07:13 +00:00
Christy Jacob 2aad9e622b feat: trigger ci 2023-09-08 19:16:25 +00:00
Christy Jacob 22c8781047 feat: test upload artifact 2023-09-08 19:11:57 +00:00
Christy Jacob 57e146be2b feat: test upload artifact 2023-09-08 19:11:00 +00:00
Christy Jacob 1e07fa96a3 feat: test upload artifact 2023-09-08 19:06:10 +00:00
Christy Jacob 2eae37d802 feat: test upload artifact 2023-09-08 19:03:55 +00:00
Christy Jacob addc88ccdc chore: test parallel executions 2023-09-08 18:49:02 +00:00
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
381 changed files with 11924 additions and 7525 deletions
+7 -7
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
@@ -50,10 +52,6 @@ _APP_STORAGE_WASABI_BUCKET=
_APP_STORAGE_ANTIVIRUS=disabled
_APP_STORAGE_ANTIVIRUS_HOST=clamav
_APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_INFLUXDB_HOST=influxdb
_APP_INFLUXDB_PORT=8086
_APP_STATSD_HOST=telegraf
_APP_STATSD_PORT=8125
_APP_SMTP_HOST=maildev
_APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
@@ -61,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
@@ -75,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
@@ -91,7 +91,7 @@ _APP_GRAPHQL_MAX_DEPTH=3
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=
_APP_VCS_GITHUB_APP_NAME=
_APP_VCS_GITHUB_PRIVATE_KEY=""
_APP_VCS_GITHUB_PRIVATE_KEY=disabled
_APP_VCS_GITHUB_APP_ID=
_APP_VCS_GITHUB_CLIENT_ID=
_APP_VCS_GITHUB_CLIENT_SECRET=
+4
View File
@@ -18,6 +18,10 @@ jobs:
- run: git checkout HEAD^2
- name: Validate composer.json and composer.lock
run: |
docker run --rm -v $PWD:/app composer sh -c \
"composer validate"
- name: Run Linter
run: |
docker run --rm -v $PWD:/app composer sh -c \
+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 }}
+128 -42
View File
@@ -4,57 +4,143 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
IMAGE: appwrite-dev
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
on: [pull_request]
jobs:
tests:
name: Unit & E2E
setup:
name: Setup & Build Appwrite Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Appwrite
uses: docker/build-push-action@v3
with:
context: .
push: false
tags: ${{ env.IMAGE }}
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
build-args: |
DEBUG=false
TESTING=true
VERSION=dev
- name: Cache Docker Image
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
unit_test:
name: Unit Test
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# Fetch submodules
submodules: recursive
- name: checkout
uses: actions/checkout@v2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Load Cache
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
# This is a separate action that sets up buildx runner
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 10
- name: Build Appwrite
uses: docker/build-push-action@v3
with:
context: .
push: false
tags: appwrite-dev
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
DEBUG=false
TESTING=true
VERSION=dev
- name: Doctor
run: docker compose exec -T appwrite doctor
- name: Start Appwrite
run: |
docker compose up -d
sleep 30
- name: Environment Variables
run: docker compose exec -T appwrite vars
- name: Doctor
run: |
docker compose logs appwrite
docker compose exec -T appwrite doctor
- name: Run Unit Tests
run: docker compose exec appwrite test /usr/src/code/tests/unit
- name: Environment Variables
run: docker compose exec -T appwrite vars
e2e_general_test:
name: E2E General Test
runs-on: ubuntu-latest
needs: setup
steps:
- name: checkout
uses: actions/checkout@v3
- name: Run Tests
run: docker compose exec -T appwrite test --debug
- 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:
fail-fast: false
matrix:
service:
[
Account,
Avatars,
Console,
Databases,
Functions,
GraphQL,
Health,
Locale,
Projects,
Realtime,
Storage,
Teams,
Users,
Webhooks,
VCS,
]
steps:
- name: checkout
uses: actions/checkout@v3
- name: Load Cache
uses: actions/cache@v3
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 10
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
+1
View File
@@ -13,3 +13,4 @@ debug/
app/sdks
dev/yasd_init.php
.phpunit.result.cache
appwrite.json
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 3.2.3
branch = 3.2.16
+1 -1
View File
@@ -8,7 +8,7 @@ tasks:
command: |
docker run --rm --interactive --tty \
--volume $PWD:/app \
composer update \
composer install \
--ignore-platform-reqs \
--optimize-autoloader \
--no-plugins \
+100
View File
@@ -1,3 +1,103 @@
# Version 1.4.14
## Changes
- New usage metrics collection flow [#7005](https://github.com/appwrite/appwrite/pull/7005)
- Deprecated influxdb, telegraf containers and removed all of their occurrences from the code.
- Removed _APP_INFLUXDB_HOST, _APP_INFLUXDB_PORT, _APP_STATSD_HOST, _APP_STATSD_PORT env variables.
- Removed usage labels dependency.
- Dropped type attribute from stats collection.
- Usage metrics are processed via new usage worker.
- updated Metric names.
# Version 1.4.13
## Notable changes
* Change enum size validation in update controller [#7164](https://github.com/appwrite/appwrite/pull/7164)
* Bump console to version 3.2.8 in [#7167](https://github.com/appwrite/appwrite/pull/7167)
## Bug fixes
* Fix error after adding bigger enum [#7162](https://github.com/appwrite/appwrite/pull/7162)
* Add chunkId to abuse key to prevent rate limit for SDKs [#7154](https://github.com/appwrite/appwrite/pull/7154)
## Miscellaneous
* Fix enum test case [#7163](https://github.com/appwrite/appwrite/pull/7163)
* Add flag to send logs to logger [#7155](https://github.com/appwrite/appwrite/pull/7155)
* Add a CI task to validate composer file and lock [#7142](https://github.com/appwrite/appwrite/pull/7142)
# Version 1.4.12
## Miscellaneous
* Bump console to version 3.2.7 [#7148](https://github.com/appwrite/appwrite/pull/7148)
* Chore update database to 0.45.2 [#7138](https://github.com/appwrite/appwrite/pull/7138)
* Implement queue thresholds for the health API [#7123](https://github.com/appwrite/appwrite/pull/7123)
* Add Authorization::skip to the usage worker [#7124](https://github.com/appwrite/appwrite/pull/7124)
## Bug fixes
* fix: use queueForDeletes in git installation delete endpoint [#7140](https://github.com/appwrite/appwrite/pull/7140)
* fix: patch script, make errors silent [#7134](https://github.com/appwrite/appwrite/pull/7134)
* fix: repositories recreation script [#7133](https://github.com/appwrite/appwrite/pull/7133)
* fix: Only delete repositories linked to the particular project [#7131](https://github.com/appwrite/appwrite/pull/7131)
# Version 1.4.11
## Miscellaneous
* Update database by @abnegate in [#7111](https://github.com/appwrite/appwrite/pull/7111)
# Version 1.4.10
## Bug fixes
* Handle cases where password history could contain NULLs [#7092](https://github.com/appwrite/appwrite/pull/7092)
* Missing functionId error on create execution [#7091](https://github.com/appwrite/appwrite/pull/7091)
* Ensure usage endpoints don't throw 500 when usage is disabled [#7087](https://github.com/appwrite/appwrite/pull/7087)
* Missing sessionId error when deleting all user sessions [#7085](https://github.com/appwrite/appwrite/pull/7085)
* Domain validation in Create Proxy rule results in 500 error [#7084](https://github.com/appwrite/appwrite/pull/7084)
* Fix optional services [#7078](https://github.com/appwrite/appwrite/pull/7078)
* Fix regression from worker refactor [#7074](https://github.com/appwrite/appwrite/pull/7074)
* Use getQueueSize() in the Health service's get X queue endpoints [#7073](https://github.com/appwrite/appwrite/pull/7073)
* Delete linked VCS repos and comments [#7066](https://github.com/appwrite/appwrite/pull/7066)
# Version 1.4.9
## Bug fixes
* Fix 400 error on function domain execution in [#7059](https://github.com/appwrite/appwrite/pull/7059)
# Version 1.4.8
## Notable changes
* Fix certificate emails and add support for variables in email template subject in [#6495](https://github.com/appwrite/appwrite/)pull/6495
* Bump console to version 3.2.5 in [#7027](https://github.com/appwrite/appwrite/pull/7027)
* Bump utopia database and storage versions in [#7002](https://github.com/appwrite/appwrite/pull/7002)
## Bug fixes
* Fixes cookie headers not being passed properly by router in [#7024](https://github.com/appwrite/appwrite/pull/7024)
* Fix permission problem in deletes worker in [#7013](https://github.com/appwrite/appwrite/pull/7013)
## Miscellaneous
* Improve error handling in the realtime service in [#6998](https://github.com/appwrite/appwrite/pull/6998)
* Update the error code for unsupported protocol in [#7006](https://github.com/appwrite/appwrite/pull/7006)
* Improve CI tests by executing them in parallel in [#6198](https://github.com/appwrite/appwrite/pull/6198)
* Update README.md to add links to orchestration tools in [#7011](https://github.com/appwrite/appwrite/pull/7011)
* Update gitpod setup to install instead of update dependencies in [#6938](https://github.com/appwrite/appwrite/pull/6938)
* Remove analytics from install script in [#7017](https://github.com/appwrite/appwrite/pull/7017)
* Improve database logging in [#7003](https://github.com/appwrite/appwrite/pull/7003)
* Add VCS tests in [#6894](https://github.com/appwrite/appwrite/pull/6894)
* Improve error messages in [#6487](https://github.com/appwrite/appwrite/pull/6487)
* Add command to delete orphaned projects in [#7015](https://github.com/appwrite/appwrite/pull/7015)
# Version 1.4.7
## Fixes
- Fix missing body in async function execution in [#6988](https://github.com/appwrite/appwrite/pull/6988)
# Version 1.4.6
## Changes
+2 -3
View File
@@ -89,7 +89,7 @@ $ git push origin [name_of_your_new_branch]
To set up a working **development environment**, just fork the project git repository and install the backend and frontend dependencies using the proper package manager and create run the docker-compose stack.
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/getting-started-for-web), or the main [README](README.md) file.
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/quick-starts), or the main [README](README.md) file.
```bash
git clone git@github.com:[YOUR_FORK_HERE]/appwrite.git
@@ -210,7 +210,6 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
│ ├── Task
│ ├── Template
│ ├── URL
│ ├── Usage
│ └── Utopia
└── tests # End to end & unit tests
├── e2e
@@ -454,7 +453,7 @@ From time to time, our team will add tutorials that will help contributors find
- [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md)
- [Appwrite Environment Variables](./docs/tutorials/add-environment-variable.md)
- [Running in Production](https://appwrite.io/docs/production)
- [Running in Production](https://appwrite.io/docs/advanced/self-hosting/production)
- [Adding Storage Adapter](./docs/tutorials/add-storage-adapter.md)
## Other Ways to Help
+14 -3
View File
@@ -74,7 +74,6 @@ RUN mkdir -p /storage/uploads && \
# Executables
RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/usage && \
chmod +x /usr/local/bin/install && \
chmod +x /usr/local/bin/upgrade && \
chmod +x /usr/local/bin/migrate && \
@@ -85,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 && \
@@ -94,16 +97,24 @@ 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-migrations && \
chmod +x /usr/local/bin/worker-hamster && \
chmod +x /usr/local/bin/worker-usage && \
chmod +x /usr/local/bin/worker-usage-dump
# Cloud Executabless
RUN chmod +x /usr/local/bin/hamster && \
chmod +x /usr/local/bin/volume-sync && \
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
chmod +x /usr/local/bin/patch-recreate-repositories-documents && \
chmod +x /usr/local/bin/patch-delete-project-collections && \
chmod +x /usr/local/bin/delete-orphaned-projects && \
chmod +x /usr/local/bin/clear-card-cache && \
chmod +x /usr/local/bin/calc-users-stats && \
chmod +x /usr/local/bin/calc-tier-stats
chmod +x /usr/local/bin/calc-tier-stats && \
chmod +x /usr/local/bin/get-migration-stats && \
chmod +x /usr/local/bin/create-inf-metric
# Letsencrypt Permissions
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
+4 -4
View File
@@ -55,7 +55,7 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
## 安装
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 [Kubernetes](https://kubernetes.io/docs/home/)、[Docker Swarm](https://docs.docker.com/engine/swarm/) 或 [Rancher](https://rancher.com/docs/))上运行 Appwrite。
启动 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html)
@@ -66,7 +66,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.13
```
### Windows
@@ -78,7 +78,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.13
```
#### PowerShell
@@ -88,7 +88,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.13
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+4 -4
View File
@@ -65,7 +65,7 @@ Table of Contents:
## Installation
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as Kubernetes, Docker Swarm, or Rancher.
Appwrite is designed to run in a containerized environment. Running your server is as easy as running one command from your terminal. You can either run Appwrite on your localhost using docker-compose or on any other container orchestration tool, such as [Kubernetes](https://kubernetes.io/docs/home/), [Docker Swarm](https://docs.docker.com/engine/swarm/), or [Rancher](https://rancher.com/docs/).
The easiest way to start running your Appwrite server is by running our docker-compose file. Before running the installation command, make sure you have [Docker](https://www.docker.com/products/docker-desktop) installed on your machine:
@@ -76,7 +76,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.13
```
### Windows
@@ -88,7 +88,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.13
```
#### PowerShell
@@ -98,7 +98,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.4.6
appwrite/appwrite:1.4.13
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+39 -29
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', []);
@@ -61,13 +62,17 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
->getResource();
$dbForConsole = new Database($dbAdapter, $cache);
$dbForConsole->setNamespace('_console');
$dbForConsole
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
// Ensure tables exist
$collections = Config::getParam('collections', [])['console'];
$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.');
}
@@ -98,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;
}
@@ -111,42 +128,35 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
$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());
// }
$database
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
};
}, ['pools', 'dbForConsole', 'cache']);
CLI::setResource('influxdb', function (Registry $register) {
$client = $register->get('influxdb'); /** @var InfluxDB\Client $client */
$attempts = 0;
$max = 10;
$sleep = 1;
do { // check if telegraf database is ready
try {
$attempts++;
$database = $client->selectDB('telegraf');
if (in_array('telegraf', $client->listDatabases())) {
break; // leave the do-while if successful
}
} catch (\Throwable $th) {
Console::warning("InfluxDB not ready. Retrying connection ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception('InfluxDB database not ready yet');
}
sleep($sleep);
}
} while ($attempts < $max);
return $database;
}, ['register']);
CLI::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
}, ['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']);
+62 -73
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'),
@@ -1270,10 +1327,10 @@ $commonCollections = [
]
],
'stats' => [
'stats_v2' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('stats'),
'name' => 'Stats',
'$id' => ID::custom('stats_v2'),
'name' => 'stats_v2',
'attributes' => [
[
'$id' => ID::custom('metric'),
@@ -1302,7 +1359,7 @@ $commonCollections = [
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 8,
'signed' => false,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
@@ -1330,17 +1387,6 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('type'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 1,
'signed' => false,
'required' => true,
'default' => 0, // 0 -> count, 1 -> sum
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
@@ -1359,7 +1405,7 @@ $commonCollections = [
],
[
'$id' => ID::custom('_key_metric_period_time'),
'type' => Database::INDEX_KEY,
'type' => Database::INDEX_UNIQUE,
'attributes' => ['metric', 'period', 'time'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
@@ -2883,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',
+72 -8
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,
@@ -86,7 +86,7 @@ return [
Exception::GENERAL_PROTOCOL_UNSUPPORTED => [
'name' => Exception::GENERAL_PROTOCOL_UNSUPPORTED,
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
'code' => 500,
'code' => 426,
],
Exception::GENERAL_CODES_DISABLED => [
'name' => Exception::GENERAL_CODES_DISABLED,
@@ -103,6 +103,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 => [
@@ -209,6 +214,7 @@ return [
'name' => Exception::USER_AUTH_METHOD_UNSUPPORTED,
'description' => 'The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console.',
'code' => 501,
'publish' => false,
],
Exception::USER_PHONE_ALREADY_EXISTS => [
'name' => Exception::USER_PHONE_ALREADY_EXISTS,
@@ -240,6 +246,21 @@ return [
'description' => 'OAuth2 provider returned some error.',
'code' => 424,
],
Exception::USER_EMAIL_ALREADY_VERIFIED => [
'name' => Exception::USER_EMAIL_ALREADY_VERIFIED,
'description' => 'User email is already verified',
'code' => 409,
],
Exception::USER_PHONE_ALREADY_VERIFIED => [
'name' => Exception::USER_PHONE_ALREADY_VERIFIED,
'description' => 'User phone is already verified',
'code' => 409
],
Exception::USER_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 => [
@@ -355,7 +376,7 @@ return [
],
Exception::STORAGE_BUCKET_ALREADY_EXISTS => [
'name' => Exception::STORAGE_BUCKET_ALREADY_EXISTS,
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'A storage bucket with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::STORAGE_BUCKET_NOT_FOUND => [
@@ -479,7 +500,7 @@ return [
],
Exception::COLLECTION_ALREADY_EXISTS => [
'name' => Exception::COLLECTION_ALREADY_EXISTS,
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'A collection with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::COLLECTION_LIMIT_EXCEEDED => [
@@ -511,7 +532,7 @@ return [
],
Exception::DOCUMENT_ALREADY_EXISTS => [
'name' => Exception::DOCUMENT_ALREADY_EXISTS,
'description' => 'Document with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::DOCUMENT_UPDATE_CONFLICT => [
@@ -553,7 +574,7 @@ return [
],
Exception::ATTRIBUTE_ALREADY_EXISTS => [
'name' => Exception::ATTRIBUTE_ALREADY_EXISTS,
'description' => 'Attribute with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.',
'code' => 409,
],
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
@@ -585,7 +606,7 @@ return [
],
Exception::INDEX_ALREADY_EXISTS => [
'name' => Exception::INDEX_ALREADY_EXISTS,
'description' => 'Index with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Index with the requested key already exists. Try again with a different key.',
'code' => 409,
],
Exception::INDEX_INVALID => [
@@ -602,7 +623,7 @@ return [
],
Exception::PROJECT_ALREADY_EXISTS => [
'name' => Exception::PROJECT_ALREADY_EXISTS,
'description' => 'Project with the requested ID already exists. Try again with a different ID or use "unique()" to generate a unique ID.',
'description' => 'Project with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
'code' => 409,
],
Exception::PROJECT_UNKNOWN => [
@@ -669,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,
@@ -732,4 +754,46 @@ return [
'description' => 'Migration is already in progress. You can check the status of the migration in your Appwrite Console\'s "Settings" > "Migrations".',
'code' => 409,
],
/** Realtime */
Exception::REALTIME_MESSAGE_FORMAT_INVALID => [
'name' => Exception::REALTIME_MESSAGE_FORMAT_INVALID,
'description' => 'Message format is not valid.',
'code' => 1003,
],
Exception::REALTIME_POLICY_VIOLATION => [
'name' => Exception::REALTIME_POLICY_VIOLATION,
'description' => 'Policy violation.',
'code' => 1008,
],
Exception::REALTIME_TOO_MANY_MESSAGES => [
'name' => Exception::REALTIME_TOO_MANY_MESSAGES,
'description' => 'Too many messages.',
'code' => 1013,
],
Exception::MIGRATION_PROVIDER_ERROR => [
'name' => Exception::MIGRATION_PROVIDER_ERROR,
'description' => 'An error occurred on the provider\'s side. Please try again later.',
'code' => 400,
],
/** Health */
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,
],
];
@@ -1,15 +1,9 @@
<p>{{hello}}</p>
<br>
<p>{{hello}},</p>
<p>{{body}}</p>
<a href="{{redirect}}" target="_blank">{{redirect}}</a>
<p><a href="{{redirect}}" target="_blank">{{redirect}}</a></p>
<p>{{footer}}</p>
<br>
<p>{{thanks}}</p>
<p>{{signature}}</p>
<p style="margin-bottom: 32px">
{{thanks}},
<br/>
{{signature}}
</p>
+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"
}
}
+19 -19
View File
@@ -15,7 +15,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '13.0.0',
'version' => '13.0.1',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -63,7 +63,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '11.0.0',
'version' => '11.0.1',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -81,7 +81,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '4.0.1',
'version' => '4.0.2',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '4.0.0',
'version' => '4.0.1',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -185,7 +185,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.3.0',
'version' => '0.5.0',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@@ -195,15 +195,15 @@ return [
'family' => APP_PLATFORM_CONSOLE,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
'gitBranch' => 'dev',
'gitUrl' => 'https://github.com/appwrite/sdk-for-console.git',
'gitBranch' => 'main',
'gitRepoName' => 'sdk-for-console',
'gitUserName' => 'appwrite',
],
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '4.1.0',
'version' => '4.2.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -231,7 +231,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '11.0.0',
'version' => '11.1.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -249,7 +249,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '9.0.0',
'version' => '9.1.0',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@@ -267,7 +267,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '10.0.0',
'version' => '10.1.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -285,7 +285,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '4.0.0',
'version' => '4.1.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -303,7 +303,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '10.0.0',
'version' => '10.1.1',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -321,7 +321,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => '3.0.0',
'version' => '3.1.0',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => '',
'enabled' => false,
@@ -339,7 +339,7 @@ return [
[
'key' => 'java',
'name' => 'Java',
'version' => '3.0.0',
'version' => '3.1.0',
'url' => 'https://github.com/appwrite/sdk-for-java',
'package' => '',
'enabled' => false,
@@ -357,7 +357,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.6.0',
'version' => '0.7.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -375,7 +375,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '10.0.0',
'version' => '10.1.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -393,7 +393,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '4.0.0',
'version' => '4.1.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -415,7 +415,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '4.0.1',
'version' => '4.1.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+3 -3
View File
@@ -170,7 +170,7 @@ return [
'docs' => false,
'docsUrl' => '',
'tests' => false,
'optional' => true,
'optional' => false,
'icon' => '',
],
'functions' => [
@@ -196,7 +196,7 @@ return [
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/proxy',
'tests' => false,
'optional' => true,
'optional' => false,
'icon' => '/images/services/proxy.png',
],
'mock' => [
@@ -248,7 +248,7 @@ return [
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/migrations',
'tests' => true,
'optional' => true,
'optional' => false,
'icon' => '/images/services/migrations.png',
],
];
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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
+21 -11
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.',
@@ -337,7 +338,7 @@ return [
],
[
'category' => 'InfluxDB',
'description' => 'Appwrite uses an InfluxDB server for managing time-series data and server stats. The InfluxDB env vars are used to allow Appwrite server to connect to the InfluxDB container.',
'description' => 'Deprecated since 1.4.8.',
'variables' => [
[
'name' => '_APP_INFLUXDB_HOST',
@@ -361,7 +362,7 @@ return [
],
[
'category' => 'StatsD',
'description' => 'Appwrite uses a StatsD server for aggregating and sending stats data over a fast UDP connection. The StatsD env vars are used to allow Appwrite server to connect to the StatsD container.',
'description' => 'Deprecated since 1.4.8.',
'variables' => [
[
'name' => '_APP_STATSD_HOST',
@@ -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).',
+167 -115
View File
@@ -45,6 +45,8 @@ 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';
$oauthDefaultFailure = '/auth/oauth2/failure';
@@ -58,7 +60,6 @@ App::post('/v1/account')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'create')
@@ -77,7 +78,8 @@ App::post('/v1/account')
->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$email = \strtolower($email);
if ('console' === $project->getId()) {
@@ -108,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) {
@@ -118,6 +120,8 @@ App::post('/v1/account')
}
}
$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);
try {
@@ -174,8 +178,6 @@ App::post('/v1/account/sessions/email')
->label('audits.event', 'session.create')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'sessions.{scope}.requests.create')
->label('usage.params', ['provider:email'])
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createEmailSession')
@@ -195,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();
@@ -214,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'));
@@ -248,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())),
@@ -428,8 +433,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->label('docs', false)
->label('usage.metric', 'sessions.{scope}.requests.create')
->label('usage.params', ['provider:{request.provider}'])
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
->param('state', '', new Text(2048), 'OAuth2 state params.', true)
@@ -550,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);
}
}
@@ -567,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());
}
}
@@ -626,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 {
@@ -684,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([
@@ -755,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);
@@ -794,7 +805,6 @@ App::get('/v1/account/identities')
->desc('List Identities')
->groups(['api', 'account'])
->label('scope', 'account')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'listIdentities')
@@ -842,14 +852,13 @@ 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')
->label('audits.event', 'identity.delete')
->label('audits.resource', 'identity/{request.$identityId}')
->label('audits.userId', '{user.$id}')
->label('usage.metric', 'identities.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'deleteIdentity')
@@ -859,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);
@@ -869,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')
@@ -887,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'])
@@ -929,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;
@@ -985,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';
@@ -1000,7 +1015,12 @@ App::post('/v1/account/sessions/magic-url')
$customTemplate = $project->getAttribute('templates', [])['email.magicSession-' . $locale->default] ?? [];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message->setParam('{{body}}', $body);
$message
->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"))
->setParam('{{signature}}', $locale->getText("emails.magicSession.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
@@ -1050,21 +1070,12 @@ App::post('/v1/account/sessions/magic-url')
}
$emailVariables = [
'subject' => $subject,
'hello' => $locale->getText("emails.magicSession.hello"),
'body' => $body,
'footer' => $locale->getText("emails.magicSession.footer"),
'thanks' => $locale->getText("emails.magicSession.thanks"),
'signature' => $locale->getText("emails.magicSession.signature"),
'direction' => $locale->getText('settings.direction'),
'bg-body' => '#f7f7f7',
'bg-content' => '#ffffff',
'text-content' => '#000000',
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{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
@@ -1098,8 +1109,6 @@ App::put('/v1/account/sessions/magic-url')
->label('audits.event', 'session.update')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'sessions.{scope}.requests.create')
->label('usage.params', ['provider:magic-url'])
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateMagicURLSession')
@@ -1168,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', []);
@@ -1177,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);
@@ -1214,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')
@@ -1228,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')
@@ -1315,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');
@@ -1330,6 +1339,7 @@ App::post('/v1/account/sessions/phone')
$queueForMessaging
->setRecipient($phone)
->setMessage($message)
->setProject($project)
->trigger();
$queueForEvents->setPayload(
@@ -1419,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);
@@ -1473,8 +1483,6 @@ App::post('/v1/account/sessions/anonymous')
->label('audits.event', 'session.create')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'sessions.{scope}.requests.create')
->label('usage.params', ['provider:anonymous'])
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createAnonymousSession')
@@ -1573,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())
@@ -1652,7 +1660,6 @@ App::get('/v1/account')
->desc('Get account')
->groups(['api', 'account'])
->label('scope', 'account')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'get')
@@ -1673,7 +1680,6 @@ App::get('/v1/account/prefs')
->desc('Get account preferences')
->groups(['api', 'account'])
->label('scope', 'account')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getPrefs')
@@ -1696,7 +1702,6 @@ App::get('/v1/account/sessions')
->desc('List sessions')
->groups(['api', 'account'])
->label('scope', 'account')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'listSessions')
@@ -1735,7 +1740,6 @@ App::get('/v1/account/logs')
->desc('List logs')
->groups(['api', 'account'])
->label('scope', 'account')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'listLogs')
@@ -1796,7 +1800,6 @@ App::get('/v1/account/sessions/:sessionId')
->desc('Get session')
->groups(['api', 'account'])
->label('scope', 'account')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getSession')
@@ -1844,7 +1847,6 @@ App::patch('/v1/account/name')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateName')
@@ -1879,7 +1881,6 @@ App::patch('/v1/account/password')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePassword')
@@ -1897,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
@@ -1924,6 +1926,8 @@ App::patch('/v1/account/password')
}
}
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
$user
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
@@ -1945,7 +1949,6 @@ App::patch('/v1/account/email')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateEmail')
@@ -1962,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');
@@ -1973,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
@@ -1981,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
@@ -2000,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());
@@ -2015,7 +2022,6 @@ App::patch('/v1/account/phone')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePhone')
@@ -2032,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');
@@ -2043,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
@@ -2074,7 +2084,6 @@ App::patch('/v1/account/prefs')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePrefs')
@@ -2108,7 +2117,6 @@ App::patch('/v1/account/status')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateStatus')
@@ -2152,7 +2160,6 @@ App::delete('/v1/account/sessions/:sessionId')
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
->label('usage.metric', 'sessions.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'deleteSession')
@@ -2207,7 +2214,7 @@ App::delete('/v1/account/sessions/:sessionId')
;
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -2229,7 +2236,6 @@ App::patch('/v1/account/sessions/:sessionId')
->label('audits.event', 'session.update')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'sessions.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateSession')
@@ -2288,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;
@@ -2314,7 +2320,6 @@ App::delete('/v1/account/sessions')
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
->label('usage.metric', 'sessions.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'deleteSessions')
@@ -2359,7 +2364,7 @@ App::delete('/v1/account/sessions')
}
}
$dbForProject->deleteCachedDocument('users', $user->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
$queueForEvents
->setParam('userId', $user->getId())
@@ -2376,7 +2381,6 @@ App::post('/v1/account/recovery')
->label('audits.event', 'recovery.create')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createRecovery')
@@ -2385,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')
@@ -2445,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]);
@@ -2457,7 +2461,12 @@ App::post('/v1/account/recovery')
$customTemplate = $project->getAttribute('templates', [])['email.recovery-' . $locale->default] ?? [];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message->setParam('{{body}}', $body);
$message
->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"))
->setParam('{{signature}}', $locale->getText("emails.recovery.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
@@ -2507,27 +2516,17 @@ App::post('/v1/account/recovery')
}
$emailVariables = [
'subject' => $subject,
'hello' => $locale->getText("emails.recovery.hello"),
'body' => $body,
'footer' => $locale->getText("emails.recovery.footer"),
'thanks' => $locale->getText("emails.recovery.thanks"),
'signature' => $locale->getText("emails.recovery.signature"),
'direction' => $locale->getText('settings.direction'),
'bg-body' => '#f7f7f7',
'bg-content' => '#ffffff',
'text-content' => '#000000',
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{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)
@@ -2559,7 +2558,6 @@ App::put('/v1/account/recovery')
->label('audits.event', 'recovery.update')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateRecovery')
@@ -2578,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);
}
@@ -2612,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)
@@ -2629,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())
@@ -2646,7 +2647,6 @@ App::post('/v1/account/verification')
->label('event', 'users.[userId].verification.[tokenId].create')
->label('audits.event', 'verification.create')
->label('audits.resource', 'user/{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createVerification')
@@ -2671,6 +2671,10 @@ App::post('/v1/account/verification')
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
if ($user->getAttribute('emailVerification')) {
throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED);
}
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -2697,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]);
@@ -2709,7 +2713,12 @@ App::post('/v1/account/verification')
$customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? [];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message->setParam('{{body}}', $body);
$message
->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"))
->setParam('{{signature}}', $locale->getText("emails.verification.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
@@ -2759,21 +2768,12 @@ App::post('/v1/account/verification')
}
$emailVariables = [
'subject' => $subject,
'hello' => $locale->getText("emails.verification.hello"),
'body' => $body,
'footer' => $locale->getText("emails.verification.footer"),
'thanks' => $locale->getText("emails.verification.thanks"),
'signature' => $locale->getText("emails.verification.signature"),
'direction' => $locale->getText('settings.direction'),
'bg-body' => '#f7f7f7',
'bg-content' => '#ffffff',
'text-content' => '#000000',
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{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
@@ -2807,7 +2807,6 @@ App::put('/v1/account/verification')
->label('event', 'users.[userId].verification.[tokenId].update')
->label('audits.event', 'verification.update')
->label('audits.resource', 'user/{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateVerification')
@@ -2851,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)
@@ -2863,12 +2862,12 @@ 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}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createPhoneVerification')
@@ -2877,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')
@@ -2896,6 +2895,10 @@ App::post('/v1/account/verification/phone')
throw new Exception(Exception::USER_PHONE_NOT_FOUND);
}
if ($user->getAttribute('phoneVerification')) {
throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED);
}
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -2922,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');
@@ -2937,6 +2940,7 @@ App::post('/v1/account/verification/phone')
$queueForMessaging
->setRecipient($user->getAttribute('phone'))
->setMessage($message)
->setProject($project)
->trigger()
;
@@ -2964,7 +2968,6 @@ App::put('/v1/account/verification/phone')
->label('event', 'users.[userId].verification.[tokenId].update')
->label('audits.event', 'verification.update')
->label('audits.resource', 'user/{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePhoneVerification')
@@ -3006,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())
@@ -3015,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 {
File diff suppressed because it is too large Load Diff
+174 -186
View File
@@ -6,13 +6,13 @@ use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Usage;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Messaging\Adapter\Realtime;
use Utopia\Validator\Assoc;
use Appwrite\Usage\Stats;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -46,6 +46,7 @@ use Utopia\Validator\Boolean;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use MaxMind\Db\Reader;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
include_once __DIR__ . '/../shared/api.php';
@@ -58,7 +59,14 @@ $redeployVcs = function (Request $request, Document $function, Document $project
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$providerBranch = $function->getAttribute('providerBranch', 'main');
$authorUrl = "https://github.com/$owner";
$repositoryUrl = "https://github.com/$owner/$repositoryName";
@@ -234,12 +242,16 @@ App::post('/v1/functions')
// Git connect logic
if (!empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
@@ -287,9 +299,8 @@ App::post('/v1/functions')
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME)
;
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
$ruleCreate
->setProject($project)
@@ -445,9 +456,9 @@ 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', '7d', '30d', '90d']), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, string $range, Response $response, Database $dbForProject) {
@@ -458,97 +469,85 @@ App::get('/v1/functions/:functionId/usage')
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS),
str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE),
];
$metrics = [
"executions.$functionId.compute.total",
"executions.$functionId.compute.success",
"executions.$functionId.compute.failure",
"executions.$functionId.compute.time",
"builds.$functionId.compute.total",
"builds.$functionId.compute.success",
"builds.$functionId.compute.failure",
"builds.$functionId.compute.time",
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$stats = [];
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric]['data'] = [];
foreach ($results as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
});
}
});
$usage = new Document([
'range' => $range,
'executionsTotal' => $stats["executions.$functionId.compute.total"] ?? [],
'executionsFailure' => $stats["executions.$functionId.compute.failure"] ?? [],
'executionsSuccess' => $stats["executions.$functionId.compute.success"] ?? [],
'executionsTime' => $stats["executions.$functionId.compute.time"] ?? [],
'buildsTotal' => $stats["builds.$functionId.compute.total"] ?? [],
'buildsFailure' => $stats["builds.$functionId.compute.failure"] ?? [],
'buildsSuccess' => $stats["builds.$functionId.compute.success"] ?? [],
'buildsTime' => $stats["builds.$functionId.compute.time" ?? []]
]);
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
}
$response->dynamic($usage, Response::MODEL_USAGE_FUNCTION);
$response->dynamic(new Document([
'range' => $range,
'deploymentsTotal' => $usage[$metrics[0]]['total'],
'deploymentsStorageTotal' => $usage[$metrics[1]]['total'],
'buildsTotal' => $usage[$metrics[2]]['total'],
'buildsStorageTotal' => $usage[$metrics[3]]['total'],
'buildsTimeTotal' => $usage[$metrics[4]]['total'],
'executionsTotal' => $usage[$metrics[5]]['total'],
'executionsTimeTotal' => $usage[$metrics[6]]['total'],
'deployments' => $usage[$metrics[0]]['data'],
'deploymentsStorage' => $usage[$metrics[1]]['data'],
'builds' => $usage[$metrics[2]]['data'],
'buildsStorage' => $usage[$metrics[3]]['data'],
'buildsTime' => $usage[$metrics[4]]['data'],
'executions' => $usage[$metrics[5]]['data'],
'executionsTime' => $usage[$metrics[6]]['data'],
]), Response::MODEL_USAGE_FUNCTION);
});
App::get('/v1/functions/usage')
->desc('Get functions usage')
->groups(['api', 'functions', 'usage'])
->groups(['api', 'functions'])
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'functions')
@@ -556,97 +555,87 @@ App::get('/v1/functions/usage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTIONS)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d']), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $range, Response $response, Database $dbForProject) {
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
METRIC_FUNCTIONS,
METRIC_DEPLOYMENTS,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS,
METRIC_BUILDS_STORAGE,
METRIC_BUILDS_COMPUTE,
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_COMPUTE,
];
$metrics = [
'executions.$all.compute.total',
'executions.$all.compute.failure',
'executions.$all.compute.success',
'executions.$all.compute.time',
'builds.$all.compute.total',
'builds.$all.compute.failure',
'builds.$all.compute.success',
'builds.$all.compute.time',
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$stats = [];
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric]['data'] = [];
foreach ($results as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
});
}
});
$usage = new Document([
'range' => $range,
'executionsTotal' => $stats[$metrics[0]] ?? [],
'executionsFailure' => $stats[$metrics[1]] ?? [],
'executionsSuccess' => $stats[$metrics[2]] ?? [],
'executionsTime' => $stats[$metrics[3]] ?? [],
'buildsTotal' => $stats[$metrics[4]] ?? [],
'buildsFailure' => $stats[$metrics[5]] ?? [],
'buildsSuccess' => $stats[$metrics[6]] ?? [],
'buildsTime' => $stats[$metrics[7]] ?? [],
]);
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
$response->dynamic($usage, Response::MODEL_USAGE_FUNCTIONS);
}
$response->dynamic(new Document([
'range' => $range,
'functionsTotal' => $usage[$metrics[0]]['total'],
'deploymentsTotal' => $usage[$metrics[1]]['total'],
'deploymentsStorageTotal' => $usage[$metrics[2]]['total'],
'buildsTotal' => $usage[$metrics[3]]['total'],
'buildsStorageTotal' => $usage[$metrics[4]]['total'],
'buildsTimeTotal' => $usage[$metrics[5]]['total'],
'executionsTotal' => $usage[$metrics[6]]['total'],
'executionsTimeTotal' => $usage[$metrics[7]]['total'],
'functions' => $usage[$metrics[0]]['data'],
'deployments' => $usage[$metrics[1]]['data'],
'deploymentsStorage' => $usage[$metrics[2]]['data'],
'builds' => $usage[$metrics[3]]['data'],
'buildsStorage' => $usage[$metrics[4]]['data'],
'buildsTime' => $usage[$metrics[5]]['data'],
'executions' => $usage[$metrics[6]]['data'],
'executionsTime' => $usage[$metrics[7]]['data'],
]), Response::MODEL_USAGE_FUNCTIONS);
});
App::put('/v1/functions/:functionId')
@@ -870,8 +859,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->setContentType('application/gzip')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"')
;
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
$size = $deviceFunctions->getFileSize($path);
$rangeHeader = $request->getHeader('range');
@@ -899,7 +887,6 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
}
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $deviceFunctions->getFileSize($path));
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFunctions->read(
@@ -1511,11 +1498,11 @@ App::post('/v1/functions/:functionId/executions')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->inject('usage')
->inject('queueForUsage')
->inject('mode')
->inject('queueForFunctions')
->inject('geodb')
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, Response $response, Document $project, Database $dbForProject, Document $user, Event $queueForEvents, Stats $usage, string $mode, Func $queueForFunctions, Reader $geodb) {
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, Response $response, Document $project, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode, Func $queueForFunctions, Reader $geodb) {
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
@@ -1653,6 +1640,8 @@ App::post('/v1/functions/:functionId/executions')
->setJWT($jwt)
->setProject($project)
->setUser($user)
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->trigger();
return $response
@@ -1741,6 +1730,13 @@ App::post('/v1/functions/:functionId/executions')
->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')) {
@@ -1748,14 +1744,6 @@ App::post('/v1/functions/:functionId/executions')
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
// TODO revise this later using route label
$usage
->setParam('functionId', $function->getId())
->setParam('executions.{scope}.compute', 1)
->setParam('executionStatus', $execution->getAttribute('status', ''))
->setParam('executionTime', $execution->getAttribute('duration')); // ms
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
+258 -35
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,7 +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')
@@ -87,22 +92,18 @@ App::get('/v1/health/db')
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
$failure[] = $database;
}
} catch (\Throwable $th) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
$failure[] = $database;
}
}
}
if (!empty($failure)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failure));
}
$response->dynamic(new Document([
'statuses' => $output,
'total' => count($output),
@@ -348,11 +349,20 @@ App::get('/v1/health/queue/webhooks')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::WEBHOOK_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/logs')
@@ -366,11 +376,70 @@ App::get('/v1/health/queue/logs')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::AUDITS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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')
@@ -384,11 +453,20 @@ App::get('/v1/health/queue/certificates')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::CERTIFICATES_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/builds')
@@ -402,11 +480,20 @@ App::get('/v1/health/queue/builds')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::BUILDS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/databases')
@@ -421,11 +508,20 @@ App::get('/v1/health/queue/databases')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (string $name, Connection $queue, Response $response) {
->action(function (string $name, int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client($name, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/deletes')
@@ -439,11 +535,20 @@ App::get('/v1/health/queue/deletes')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::DELETE_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/mails')
@@ -457,11 +562,20 @@ App::get('/v1/health/queue/mails')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MAILS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/messaging')
@@ -475,11 +589,20 @@ App::get('/v1/health/queue/messaging')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MESSAGING_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/migrations')
@@ -493,11 +616,20 @@ App::get('/v1/health/queue/migrations')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MIGRATIONS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/queue/functions')
@@ -511,11 +643,20 @@ App::get('/v1/health/queue/functions')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('response')
->action(function (Connection $queue, Response $response) {
->action(function (int|string $threshold, Connection $queue, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::FUNCTIONS_QUEUE_NAME, $queue);
$response->dynamic(new Document([ 'size' => $client->sumProcessingJobs() ]), Response::MODEL_HEALTH_QUEUE);
$size = $client->getQueueSize();
if ($size >= $threshold) {
throw new Exception(Exception::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/storage/local')
@@ -536,10 +677,10 @@ App::get('/v1/health/storage/local')
foreach (
[
'Uploads' => APP_STORAGE_UPLOADS,
'Cache' => APP_STORAGE_CACHE,
'Config' => APP_STORAGE_CONFIG,
'Certs' => APP_STORAGE_CERTIFICATES
'Uploads' => APP_STORAGE_UPLOADS,
'Cache' => APP_STORAGE_CACHE,
'Config' => APP_STORAGE_CONFIG,
'Certs' => APP_STORAGE_CERTIFICATES
] as $key => $volume
) {
$device = new Local($volume);
@@ -561,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'])
@@ -600,8 +782,49 @@ 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')
->desc('Get system stats')
->groups(['api', 'health'])
->label('scope', 'root')
// ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
+91 -27
View File
@@ -208,6 +208,16 @@ App::post('/v1/migrations/firebase')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$serviceAccountData = json_decode($serviceAccount, true);
if (empty($serviceAccountData)) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
if (!isset($serviceAccountData['project_id']) || !isset($serviceAccountData['client_email']) || !isset($serviceAccountData['private_key'])) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
@@ -449,15 +459,26 @@ App::get('/v1/migrations/appwrite/report')
->inject('project')
->inject('user')
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) {
try {
$appwrite = new Appwrite($projectID, $endpoint, $key);
$appwrite = new Appwrite($projectID, $endpoint, $key);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($appwrite->report($resources)), Response::MODEL_MIGRATION_REPORT);
try {
$report = $appwrite->report($resources);
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/firebase/report')
@@ -475,15 +496,36 @@ App::get('/v1/migrations/firebase/report')
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
->action(function (array $resources, string $serviceAccount, Response $response) {
try {
$firebase = new Firebase(json_decode($serviceAccount, true));
$serviceAccount = json_decode($serviceAccount, true);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($firebase->report($resources)), Response::MODEL_MIGRATION_REPORT);
} catch (\Exception $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
if (empty($serviceAccount)) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
if (!isset($serviceAccount['project_id']) || !isset($serviceAccount['client_email']) || !isset($serviceAccount['private_key'])) {
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Invalid Service Account JSON');
}
$firebase = new Firebase($serviceAccount);
try {
$report = $firebase->report($resources);
} catch (\Throwable $e) {
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/firebase/report/oauth')
@@ -869,15 +911,26 @@ App::get('/v1/migrations/supabase/report')
->inject('response')
->inject('dbForProject')
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response) {
try {
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($supabase->report($resources)), Response::MODEL_MIGRATION_REPORT);
} catch (\Exception $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
try {
$report = $supabase->report($resources);
} catch (\Throwable $e) {
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/nhost/report')
@@ -901,15 +954,26 @@ App::get('/v1/migrations/nhost/report')
->param('port', 5432, new Integer(true), 'Source\'s Database Port.', true)
->inject('response')
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response) {
try {
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($nhost->report($resources)), Response::MODEL_MIGRATION_REPORT);
} catch (\Exception $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
try {
$report = $nhost->report($resources);
} catch (\Throwable $e) {
switch ($e->getCode()) {
case 401:
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'Source Error: ' . $e->getMessage());
case 429:
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?');
case 500:
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
throw new Exception(Exception::MIGRATION_PROVIDER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::patch('/v1/migrations/:migrationId')
+144 -81
View File
@@ -3,8 +3,10 @@
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@@ -14,11 +16,10 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Database\DateTime;
App::get('/v1/project/usage')
->desc('Get usage stats for a project')
->groups(['api'])
->groups(['api', 'usage'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
@@ -26,100 +27,162 @@ App::get('/v1/project/usage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_PROJECT)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('startDate', '', new DateTimeValidator(), 'Starting date for the usage')
->param('endDate', '', new DateTimeValidator(), 'End date for the usage')
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
->inject('response')
->inject('dbForProject')
->action(function (string $range, Response $response, Database $dbForProject) {
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
$lastDay = (new DateTime($endDate))->format($format);
$metrics = [
'project.$all.network.requests',
'project.$all.network.bandwidth',
'project.$all.storage.size',
'users.$all.count.total',
'databases.$all.count.total',
'documents.$all.count.total',
'executions.$all.compute.total',
'buckets.$all.count.total'
];
$metrics = [
'total' => [
METRIC_EXECUTIONS,
METRIC_DOCUMENTS,
METRIC_DATABASES,
METRIC_USERS,
METRIC_BUCKETS,
METRIC_FILES_STORAGE
],
'period' => [
METRIC_NETWORK_REQUESTS,
METRIC_NETWORK_INBOUND,
METRIC_NETWORK_OUTBOUND,
METRIC_USERS,
METRIC_EXECUTIONS
]
];
$stats = [];
$factor = match ($period) {
'1h' => 3600,
'1d' => 86400,
};
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$limit = match ($period) {
'1h' => (new DateTime($startDate))->diff(new DateTime($endDate))->days * 24,
'1d' => (new DateTime($startDate))->diff(new DateTime($endDate))->days
};
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$format = match ($period) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, &$total, &$stats) {
foreach ($metrics['total'] as $metric) {
$result = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$total[$metric] = $result['value'] ?? 0;
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
foreach ($metrics['period'] as $metric) {
$results = $dbForProject->find('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::greaterThanEqual('time', $firstDay),
Query::lessThan('time', $lastDay),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($results as $result) {
$stats[$metric][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
});
}
});
$usage = new Document([
'range' => $range,
'requests' => $stats[$metrics[0]] ?? [],
'network' => $stats[$metrics[1]] ?? [],
'storage' => $stats[$metrics[2]] ?? [],
'users' => $stats[$metrics[3]] ?? [],
'databases' => $stats[$metrics[4]] ?? [],
'documents' => $stats[$metrics[5]] ?? [],
'executions' => $stats[$metrics[6]] ?? [],
'buckets' => $stats[$metrics[7]] ?? [],
]);
$now = time();
foreach ($metrics['period'] as $metric) {
$usage[$metric] = [];
$leap = $now - ($limit * $factor);
while ($leap < $now) {
$leap += $factor;
$formatDate = date($format, $leap);
$usage[$metric][] = [
'value' => $stats[$metric][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
}
$response->dynamic($usage, Response::MODEL_USAGE_PROJECT);
$executionsBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS);
$value = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $dbForProject->find('functions'));
$bucketsBreakdown = array_map(function ($bucket) use ($dbForProject) {
$id = $bucket->getId();
$name = $bucket->getAttribute('name');
$metric = str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE);
$value = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
return [
'resourceId' => $id,
'name' => $name,
'value' => $value['value'] ?? 0,
];
}, $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' => $network,
'users' => ($usage[METRIC_USERS]),
'executions' => ($usage[METRIC_EXECUTIONS]),
'executionsTotal' => $total[METRIC_EXECUTIONS],
'documentsTotal' => $total[METRIC_DOCUMENTS],
'databasesTotal' => $total[METRIC_DATABASES],
'usersTotal' => $total[METRIC_USERS],
'bucketsTotal' => $total[METRIC_BUCKETS],
'filesStorageTotal' => $total[METRIC_FILES_STORAGE],
'executionsBreakdown' => $executionsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown
]), Response::MODEL_USAGE_PROJECT);
});
// Variables
// Variables
App::post('/v1/project/variables')
->desc('Create Variable')
->groups(['api'])
+76 -166
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,19 +86,26 @@ 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;
}
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
$backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '04:00'];
$backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '01:00'];
$backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '05:00'];
$backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '02:00'];
$backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00'];
$databases = Config::getParam('pools-database', []);
@@ -121,9 +129,9 @@ App::post('/v1/projects')
}
}
$databaseOverride = App::getEnv('_APP_DATABASE_OVERRIDE', null);
$databaseOverride = App::getEnv('_APP_DATABASE_OVERRIDE');
$index = array_search($databaseOverride, $databases);
if ($index) {
if ($index !== false) {
$database = $databases[$index];
} else {
$database = $databases[array_rand($databases)];
@@ -133,6 +141,25 @@ 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;
}
\var_dump('DATABASE: ' . $database);
try {
$project = $dbForConsole->createDocument('projects', new Document([
'$id' => $projectId,
@@ -164,14 +191,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 +227,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
@@ -293,120 +318,6 @@ App::get('/v1/projects/:projectId')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::get('/v1/projects/:projectId/usage')
->desc('Get usage stats for a project')
->groups(['api', 'projects', 'usage'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForConsole')
->inject('dbForProject')
->inject('register')
->action(function (string $projectId, string $range, Response $response, Database $dbForConsole, Database $dbForProject, Registry $register) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$dbForProject->setNamespace("_{$project->getInternalId()}");
$metrics = [
'project.$all.network.requests',
'project.$all.network.bandwidth',
'project.$all.storage.size',
'users.$all.count.total',
'databases.$all.count.total',
'documents.$all.count.total',
'executions.$all.compute.total',
'buckets.$all.count.total'
];
$stats = [];
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'requests' => $stats[$metrics[0]] ?? [],
'network' => $stats[$metrics[1]] ?? [],
'storage' => $stats[$metrics[2]] ?? [],
'users' => $stats[$metrics[3]] ?? [],
'databases' => $stats[$metrics[4]] ?? [],
'documents' => $stats[$metrics[5]] ?? [],
'executions' => $stats[$metrics[6]] ?? [],
'buckets' => $stats[$metrics[7]] ?? [],
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_PROJECT);
});
App::patch('/v1/projects/:projectId')
->desc('Update project')
->groups(['api', 'projects'])
@@ -934,7 +845,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)
@@ -1055,7 +966,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);
});
@@ -1094,7 +1005,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);
});
@@ -1131,7 +1042,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();
});
@@ -1181,7 +1092,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)
@@ -1295,7 +1206,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);
});
@@ -1332,7 +1243,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();
});
@@ -1382,7 +1293,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)
@@ -1497,7 +1408,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);
});
@@ -1534,7 +1445,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
$dbForConsole->deleteDocument('platforms', $platformId);
$dbForConsole->deleteCachedDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$response->noContent();
});
@@ -1560,7 +1471,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) {
@@ -1703,9 +1614,8 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
->setParam('{{user}}', '')
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'))
->setParam('{{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'));
+15 -2
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;
@@ -87,7 +88,11 @@ App::post('/v1/proxy/rules')
$resourceInternalId = $function->getInternalId();
}
$domain = new Domain($domain);
try {
$domain = new Domain($domain);
} catch (\Exception) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
}
$ruleId = ID::unique();
$rule = new Document([
@@ -274,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()) {
@@ -294,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);
}
+176 -218
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')
@@ -52,7 +53,6 @@ App::post('/v1/storage/buckets')
->label('event', 'buckets.[bucketId].create')
->label('audits.event', 'bucket.create')
->label('audits.resource', 'bucket/{response.$id}')
->label('usage.metric', 'buckets.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createBucket')
@@ -67,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')
@@ -148,7 +148,6 @@ App::get('/v1/storage/buckets')
->desc('List buckets')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
->label('usage.metric', 'buckets.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listBuckets')
@@ -197,7 +196,6 @@ App::get('/v1/storage/buckets/:bucketId')
->desc('Get bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
->label('usage.metric', 'buckets.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getBucket')
@@ -226,7 +224,6 @@ App::put('/v1/storage/buckets/:bucketId')
->label('event', 'buckets.[bucketId].update')
->label('audits.event', 'bucket.update')
->label('audits.resource', 'bucket/{response.$id}')
->label('usage.metric', 'buckets.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'updateBucket')
@@ -241,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')
@@ -294,7 +291,6 @@ App::delete('/v1/storage/buckets/:bucketId')
->label('audits.event', 'bucket.delete')
->label('event', 'buckets.[bucketId].delete')
->label('audits.resource', 'bucket/{request.bucketId}')
->label('usage.metric', 'buckets.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteBucket')
@@ -337,8 +333,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
->label('audits.event', 'file.create')
->label('event', 'buckets.[bucketId].files.[fileId].create')
->label('audits.resource', 'file/{response.$id}')
->label('usage.metric', 'files.{scope}.requests.create')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -538,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) {
@@ -622,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);
@@ -659,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);
@@ -689,8 +708,6 @@ App::get('/v1/storage/buckets/:bucketId/files')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('usage.metric', 'files.{scope}.requests.read')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listFiles')
->label('sdk.description', '/docs/references/storage/list-files.md')
@@ -770,8 +787,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('usage.metric', 'files.{scope}.requests.read')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFile')
->label('sdk.description', '/docs/references/storage/get-file.md')
@@ -821,8 +836,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->label('cache', true)
->label('cache.resourceType', 'bucket/{request.bucketId}')
->label('cache.resource', 'file/{request.fileId}')
->label('usage.metric', 'files.{scope}.requests.read')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFilePreview')
@@ -872,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 {
@@ -890,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)) {
@@ -903,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));
@@ -915,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
@@ -935,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;
@@ -987,8 +1005,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->desc('Get file for download')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('usage.metric', 'files.{scope}.requests.read')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileDownload')
@@ -1080,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);
}
@@ -1109,7 +1125,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFiles->read(
@@ -1130,8 +1145,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->desc('Get file for view')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('usage.metric', 'files.{scope}.requests.read')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileView')
@@ -1232,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);
}
@@ -1254,15 +1267,16 @@ 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);
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $deviceFiles->getFileSize($path));
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFiles->read(
@@ -1286,8 +1300,6 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->label('event', 'buckets.[bucketId].files.[fileId].update')
->label('audits.event', 'file.update')
->label('audits.resource', 'file/{response.$id}')
->label('usage.metric', 'files.{scope}.requests.update')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -1396,8 +1408,6 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->label('event', 'buckets.[bucketId].files.[fileId].delete')
->label('audits.event', 'file.delete')
->label('audits.resource', 'file/{request.fileId}')
->label('usage.metric', 'files.{scope}.requests.delete')
->label('usage.params', ['bucketId:{request.bucketId}'])
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -1489,7 +1499,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
App::get('/v1/storage/usage')
->desc('Get usage stats for storage')
->groups(['api', 'storage', 'usage'])
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
@@ -1497,109 +1507,78 @@ App::get('/v1/storage/usage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_STORAGE)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $range, Response $response, Database $dbForProject) {
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') {
$periods = [
'24h' => [
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
METRIC_BUCKETS,
METRIC_FILES,
METRIC_FILES_STORAGE,
];
$metrics = [
'project.$all.storage.size',
'buckets.$all.count.total',
'buckets.$all.requests.create',
'buckets.$all.requests.read',
'buckets.$all.requests.update',
'buckets.$all.requests.delete',
'files.$all.storage.size',
'files.$all.count.total',
'files.$all.requests.create',
'files.$all.requests.read',
'files.$all.requests.update',
'files.$all.requests.delete',
];
$total = [];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$stats = [];
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric]['data'] = [];
foreach ($results as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
});
}
});
$usage = new Document([
'range' => $range,
'bucketsCount' => $stats['buckets.$all.count.total'],
'bucketsCreate' => $stats['buckets.$all.requests.create'],
'bucketsRead' => $stats['buckets.$all.requests.read'],
'bucketsUpdate' => $stats['buckets.$all.requests.update'],
'bucketsDelete' => $stats['buckets.$all.requests.delete'],
'storage' => $stats['project.$all.storage.size'],
'filesCount' => $stats['files.$all.count.total'],
'filesCreate' => $stats['files.$all.requests.create'],
'filesRead' => $stats['files.$all.requests.read'],
'filesUpdate' => $stats['files.$all.requests.update'],
'filesDelete' => $stats['files.$all.requests.delete'],
]);
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
$response->dynamic($usage, Response::MODEL_USAGE_STORAGE);
}
$response->dynamic(new Document([
'range' => $range,
'bucketsTotal' => $usage[$metrics[0]]['total'],
'filesTotal' => $usage[$metrics[1]]['total'],
'filesStorageTotal' => $usage[$metrics[2]]['total'],
'buckets' => $usage[$metrics[0]]['data'],
'files' => $usage[$metrics[1]]['data'],
'storage' => $usage[$metrics[2]]['data'],
]), Response::MODEL_USAGE_STORAGE);
});
App::get('/v1/storage/:bucketId/usage')
->desc('Get usage stats for a storage bucket')
->groups(['api', 'storage', 'usage'])
->desc('Get usage stats for storage bucket')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
@@ -1608,7 +1587,7 @@ App::get('/v1/storage/:bucketId/usage')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_BUCKETS)
->param('bucketId', '', new UID(), 'Bucket ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) {
@@ -1619,86 +1598,65 @@ App::get('/v1/storage/:bucketId/usage')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') {
$periods = [
'24h' => [
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES),
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE),
];
$metrics = [
"files.{$bucketId}.count.total",
"files.{$bucketId}.storage.size",
"files.{$bucketId}.requests.create",
"files.{$bucketId}.requests.read",
"files.{$bucketId}.requests.update",
"files.{$bucketId}.requests.delete",
];
$stats = [];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric]['data'] = [];
foreach ($results as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
});
}
});
$usage = new Document([
'range' => $range,
'filesCount' => $stats[$metrics[0]],
'filesStorage' => $stats[$metrics[1]],
'filesCreate' => $stats[$metrics[2]],
'filesRead' => $stats[$metrics[3]],
'filesUpdate' => $stats[$metrics[4]],
'filesDelete' => $stats[$metrics[5]],
]);
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
}
$response->dynamic($usage, Response::MODEL_USAGE_BUCKETS);
$response->dynamic(new Document([
'range' => $range,
'filesTotal' => $usage[$metrics[0]]['total'],
'filesStorageTotal' => $usage[$metrics[1]]['total'],
'files' => $usage[$metrics[0]]['data'],
'storage' => $usage[$metrics[1]]['data'],
]), Response::MODEL_USAGE_BUCKETS);
});
+20 -24
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);
@@ -555,7 +555,12 @@ App::post('/v1/teams/:teamId/memberships')
$customTemplate = $project->getAttribute('templates', [])['email.invitation-' . $locale->default] ?? [];
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message->setParam('{{body}}', $body);
$message
->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
@@ -606,21 +611,12 @@ App::post('/v1/teams/:teamId/memberships')
$emailVariables = [
'owner' => $user->getAttribute('name'),
'subject' => $subject,
'hello' => $locale->getText("emails.invitation.hello"),
'body' => $body,
'footer' => $locale->getText("emails.invitation.footer"),
'thanks' => $locale->getText("emails.invitation.thanks"),
'signature' => $locale->getText("emails.invitation.signature"),
'direction' => $locale->getText('settings.direction'),
'bg-body' => '#f7f7f7',
'bg-content' => '#ffffff',
'text-content' => '#000000',
/* {{user}} ,{{team}}, {{project}} and {{redirect}} are required in the templates */
/* {{user}}, {{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
@@ -645,6 +641,7 @@ App::post('/v1/teams/:teamId/memberships')
$queueForMessaging
->setRecipient($phone)
->setMessage($message)
->setProject($project)
->trigger();
}
}
@@ -847,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())
@@ -962,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())
@@ -1047,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
+109 -140
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()),
@@ -85,7 +88,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'status' => true,
'labels' => [],
'password' => $password,
'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password],
'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password],
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
@@ -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);
}
@@ -114,7 +123,6 @@ App::post('/v1/users')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'create')
@@ -131,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);
@@ -146,7 +154,6 @@ App::post('/v1/users/bcrypt')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createBcryptUser')
@@ -162,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)
@@ -177,7 +185,6 @@ App::post('/v1/users/md5')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createMD5User')
@@ -193,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)
@@ -208,7 +216,6 @@ App::post('/v1/users/argon2')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createArgon2User')
@@ -224,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)
@@ -239,7 +247,6 @@ App::post('/v1/users/sha')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createSHAUser')
@@ -256,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)
@@ -277,7 +285,6 @@ App::post('/v1/users/phpass')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createPHPassUser')
@@ -293,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)
@@ -308,7 +316,6 @@ App::post('/v1/users/scrypt')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createScryptUser')
@@ -329,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,
@@ -338,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)
@@ -352,7 +360,6 @@ App::post('/v1/users/scrypt-modified')
->label('scope', 'users.write')
->label('audits.event', 'user.create')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.create')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'createScryptModifiedUser')
@@ -371,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)
@@ -383,7 +391,6 @@ App::get('/v1/users')
->desc('List users')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'list')
@@ -432,7 +439,6 @@ App::get('/v1/users/:userId')
->desc('Get user')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'get')
@@ -458,7 +464,6 @@ App::get('/v1/users/:userId/prefs')
->desc('Get user preferences')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'getPrefs')
@@ -486,7 +491,6 @@ App::get('/v1/users/:userId/sessions')
->desc('List user sessions')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'listSessions')
@@ -528,7 +532,6 @@ App::get('/v1/users/:userId/memberships')
->desc('List user memberships')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'listMemberships')
@@ -568,7 +571,6 @@ App::get('/v1/users/:userId/logs')
->desc('List user logs')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'listLogs')
@@ -650,7 +652,6 @@ App::get('/v1/users/identities')
->desc('List Identities')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'listIdentities')
@@ -703,7 +704,6 @@ App::patch('/v1/users/:userId/status')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateStatus')
@@ -739,7 +739,6 @@ App::put('/v1/users/:userId/labels')
->label('scope', 'users.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateLabels')
@@ -777,7 +776,6 @@ App::patch('/v1/users/:userId/verification/phone')
->label('scope', 'users.write')
->label('audits.event', 'verification.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updatePhoneVerification')
@@ -814,7 +812,6 @@ App::patch('/v1/users/:userId/name')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateName')
@@ -852,7 +849,6 @@ App::patch('/v1/users/:userId/password')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updatePassword')
@@ -866,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);
@@ -881,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;
@@ -917,7 +916,6 @@ App::patch('/v1/users/:userId/email')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateEmail')
@@ -973,7 +971,6 @@ App::patch('/v1/users/:userId/phone')
->label('scope', 'users.write')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updatePhone')
@@ -1018,7 +1015,6 @@ App::patch('/v1/users/:userId/verification')
->label('audits.event', 'verification.update')
->label('audits.resource', 'user/{request.userId}')
->label('audits.userId', '{request.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateEmailVerification')
@@ -1051,7 +1047,6 @@ App::patch('/v1/users/:userId/prefs')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.prefs')
->label('scope', 'users.write')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'updatePrefs')
@@ -1087,7 +1082,6 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->label('scope', 'users.write')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{request.userId}')
->label('usage.metric', 'sessions.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteSession')
@@ -1114,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())
@@ -1127,11 +1121,10 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
App::delete('/v1/users/:userId/sessions')
->desc('Delete user sessions')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('event', 'users.[userId].sessions.delete')
->label('scope', 'users.write')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
->label('usage.metric', 'sessions.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteSessions')
@@ -1158,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())
@@ -1174,7 +1167,6 @@ App::delete('/v1/users/:userId')
->label('scope', 'users.write')
->label('audits.event', 'user.delete')
->label('audits.resource', 'user/{request.userId}')
->label('usage.metric', 'users.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'delete')
@@ -1211,13 +1203,12 @@ 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')
->label('audits.event', 'identity.delete')
->label('audits.resource', 'identity/{request.$identityId}')
->label('usage.metric', 'users.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'users')
->label('sdk.method', 'deleteIdentity')
@@ -1227,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);
@@ -1237,12 +1229,17 @@ 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();
});
App::get('/v1/users/usage')
->desc('Get usage stats for the users API')
->groups(['api', 'users', 'usage'])
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
@@ -1250,97 +1247,69 @@ App::get('/v1/users/usage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->inject('register')
->action(function (string $range, string $provider, Response $response, Database $dbForProject) {
->action(function (string $range, Response $response, Database $dbForProject) {
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '1h',
'limit' => 24,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
METRIC_USERS,
METRIC_SESSIONS,
];
$metrics = [
'users.$all.count.total',
'users.$all.requests.create',
'users.$all.requests.read',
'users.$all.requests.update',
'users.$all.requests.delete',
'sessions.$all.requests.create',
'sessions.$all.requests.delete',
"sessions.$provider.requests.create",
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $count => $metric) {
$result = $dbForProject->findOne('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$stats = [];
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'1h' => 3600,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats_v2', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric]['data'] = [];
foreach ($results as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
});
}
});
$usage = new Document([
'range' => $range,
'usersCount' => $stats['users.$all.count.total'] ?? [],
'usersCreate' => $stats['users.$all.requests.create'] ?? [],
'usersRead' => $stats['users.$all.requests.read'] ?? [],
'usersUpdate' => $stats['users.$all.requests.update'] ?? [],
'usersDelete' => $stats['users.$all.requests.delete'] ?? [],
'sessionsCreate' => $stats['sessions.$all.requests.create'] ?? [],
'sessionsProviderCreate' => $stats["sessions.$provider.requests.create"] ?? [],
'sessionsDelete' => $stats['sessions.$all.requests.delete' ?? []]
]);
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
}
$response->dynamic($usage, Response::MODEL_USAGE_USERS);
$response->dynamic(new Document([
'range' => $range,
'usersTotal' => $usage[$metrics[0]]['total'],
'sessionsTotal' => $usage[$metrics[1]]['total'],
'users' => $usage[$metrics[0]]['data'],
'sessions' => $usage[$metrics[1]]['data'],
]), Response::MODEL_USAGE_USERS);
});
+67 -30
View File
@@ -22,7 +22,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Detector\Adapter\Bun;
use Utopia\Detector\Adapter\CPP;
use Utopia\Detector\Adapter\Dart;
@@ -36,6 +35,7 @@ use Utopia\Detector\Adapter\Ruby;
use Utopia\Detector\Adapter\Swift;
use Utopia\Detector\Detector;
use Utopia\Validator\Boolean;
use Utopia\VCS\Exception\RepositoryNotFound;
use function Swoole\Coroutine\batch;
@@ -66,7 +66,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
@@ -154,7 +161,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$message = 'Authorization required for external contributor.';
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$owner = $github->getOwnerName($providerInstallationId);
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'failure', $message, $authorizeUrl, $name);
continue;
@@ -206,7 +220,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$message = 'Starting...';
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$owner = $github->getOwnerName($providerInstallationId);
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/functions/function-$functionId";
@@ -273,7 +294,7 @@ App::get('/v1/vcs/github/callback')
->label('scope', 'public')
->label('error', __DIR__ . '/../../views/general/error.phtml')
->param('installation_id', '', new Text(256, 0), 'GitHub installation ID', true)
->param('setup_action', '', new Text(256, 0), 'GitHub setup actuon type', true)
->param('setup_action', '', new Text(256, 0), 'GitHub setup action type', true)
->param('state', '', new Text(2048), 'GitHub state. Contains info sent when starting authorization flow.', true)
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
->inject('gitHub')
@@ -458,9 +479,12 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -720,9 +744,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -766,9 +793,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -827,10 +857,10 @@ App::post('/v1/vcs/github/events')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
//find functionId from functions table
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::limit(100),
]);
]));
// create new deployment only on push and not when branch is created
if (!$providerBranchCreated) {
@@ -847,13 +877,13 @@ App::post('/v1/vcs/github/events')
]);
foreach ($installations as $installation) {
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('installationInternalId', [$installation->getInternalId()]),
Query::limit(1000)
]);
]));
foreach ($repositories as $repository) {
$dbForConsole->deleteDocument('repositories', $repository->getId());
Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
}
$dbForConsole->deleteDocument('installations', $installation->getId());
@@ -885,10 +915,10 @@ App::post('/v1/vcs/github/events')
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]);
]));
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
} elseif ($parsedPayload["action"] == "closed") {
@@ -899,10 +929,10 @@ App::post('/v1/vcs/github/events')
$external = $parsedPayload["external"] ?? true;
if ($external) {
$repositories = $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]);
]));
foreach ($repositories as $repository) {
$providerPullRequestIds = $repository->getAttribute('providerPullRequestIds', []);
@@ -1016,8 +1046,8 @@ App::delete('/v1/vcs/installations/:installationId')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('deletes')
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $deletes) {
->inject('queueForDeletes')
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes) {
$installation = $dbForConsole->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
@@ -1028,7 +1058,7 @@ App::delete('/v1/vcs/installations/:installationId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB');
}
$deletes
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($installation);
@@ -1062,9 +1092,9 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$repository = $dbForConsole->getDocument('repositories', $repositoryId, [
$repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
]));
if ($repository->isEmpty()) {
throw new Exception(Exception::REPOSITORY_NOT_FOUND);
@@ -1079,7 +1109,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
// TODO: Delete from array when PR is closed
$repository = $dbForConsole->updateDocument('repositories', $repository->getId(), $repository);
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
@@ -1090,7 +1120,14 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
$owner = $github->getOwnerName($providerInstallationId);
$repositoryName = $github->getRepositoryName($providerRepositoryId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$pullRequestResponse = $github->getPullRequest($owner, $repositoryName, $providerPullRequestId);
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
+283 -103
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,57 +120,218 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
$path .= '?' . $query;
}
$body = \json_encode([
'async' => false,
'body' => $swooleRequest->getContent() ?? '',
'method' => $swooleRequest->server['request_method'],
'path' => $path,
'headers' => $swooleRequest->header
$body = $swooleRequest->getContent() ?? '';
$method = $swooleRequest->server['request_method'];
$requestHeaders = $request->getHeaders();
$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'));
@@ -177,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)
@@ -203,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
*/
@@ -218,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;
}
}
@@ -230,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', ''));
@@ -423,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');
@@ -509,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);
@@ -523,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());
}
}
}
@@ -569,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
*/
@@ -577,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;
}
}
@@ -587,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')
@@ -601,61 +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();
if ($logger) {
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
}
if ($error instanceof AppwriteException) {
$publish = $error->isPublishable();
} else {
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
}
$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();
@@ -758,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());
}
+63 -496
View File
@@ -9,506 +9,13 @@ use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
use Utopia\Storage\Validator\File;
use Utopia\Validator\WhiteList;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Validator\Nullable;
App::get('/v1/mock/tests/foo')
->desc('Get Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::post('/v1/mock/tests/foo')
->desc('Post Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::patch('/v1/mock/tests/foo')
->desc('Patch Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a patch request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::put('/v1/mock/tests/foo')
->desc('Put Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::delete('/v1/mock/tests/foo')
->desc('Delete Foo')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'foo')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($x, $y, $z) {
});
App::get('/v1/mock/tests/bar')
->desc('Get Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::post('/v1/mock/tests/bar')
->desc('Post Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.offline.model', '/mock/tests/bar')
->label('sdk.offline.key', '{required}')
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::patch('/v1/mock/tests/bar')
->desc('Patch Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a patch request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::put('/v1/mock/tests/bar')
->desc('Put Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::delete('/v1/mock/tests/bar')
->desc('Delete Bar')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'bar')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->action(function ($required, $default, $z) {
});
App::get('/v1/mock/tests/general/headers')
->desc('Get headers')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'headers')
->label('sdk.description', 'Return headers from the request')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$res = [
'x-sdk-name' => $request->getHeader('x-sdk-name'),
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
'x-sdk-language' => $request->getHeader('x-sdk-language'),
'x-sdk-version' => $request->getHeader('x-sdk-version'),
];
$res = array_map(function ($key, $value) {
return $key . ': ' . $value;
}, array_keys($res), $res);
$res = implode("; ", $res);
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
});
App::get('/v1/mock/tests/general/download')
->desc('Download File')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'download')
->label('sdk.methodType', 'location')
->label('sdk.description', 'Mock a file download request.')
->label('sdk.response.type', '*/*')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response
->setContentType('text/plain')
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->send("GET:/v1/mock/tests/general/download:passed")
;
});
App::post('/v1/mock/tests/general/upload')
->desc('Upload File')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'upload')
->label('sdk.description', 'Mock a file upload request.')
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->param('x', '', new Text(100), 'Sample string param')
->param('y', '', new Integer(true), 'Sample numeric param')
->param('z', null, new ArrayList(new Text(256), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Sample array param')
->param('file', [], new File(), 'Sample file param', skipValidation: true)
->inject('request')
->inject('response')
->action(function (string $x, int $y, array $z, mixed $file, Request $request, Response $response) {
$file = $request->getFiles('file');
$contentRange = $request->getHeader('content-range');
$chunkSize = 5 * 1024 * 1024; // 5MB
if (!empty($contentRange)) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$size = $request->getContentRangeSize();
$id = $request->getHeader('x-appwrite-id', '');
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
if (is_null($start) || is_null($end) || is_null($size) || $end >= $size) {
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
}
if ($start > $end || $end > $size) {
throw new Exception(Exception::GENERAL_MOCK, 'Invalid content-range header');
}
if ($start === 0 && !empty($id)) {
throw new Exception(Exception::GENERAL_MOCK, 'First chunked request cannot have id header');
}
if ($start !== 0 && $id !== 'newfileid') {
throw new Exception(Exception::GENERAL_MOCK, 'All chunked request must have id header (except first)');
}
if ($end !== $size - 1 && $end - $start + 1 !== $chunkSize) {
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB (except last chunk)');
}
if ($end !== $size - 1 && $file['size'] !== $chunkSize) {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong chunk size');
}
if ($file['size'] > $chunkSize) {
throw new Exception(Exception::GENERAL_MOCK, 'Chunk size must be 5MB or less');
}
if ($end !== $size - 1) {
$response->json([
'$id' => ID::custom('newfileid'),
'chunksTotal' => (int) ceil($size / ($end + 1 - $start)),
'chunksUploaded' => ceil($start / $chunkSize) + 1
]);
}
} else {
$file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'][0] : $file['tmp_name'];
$file['name'] = (\is_array($file['name'])) ? $file['name'][0] : $file['name'];
$file['size'] = (\is_array($file['size'])) ? $file['size'][0] : $file['size'];
if ($file['name'] !== 'file.png') {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file name');
}
if ($file['size'] !== 38756) {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file size');
}
if (\md5(\file_get_contents($file['tmp_name'])) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
throw new Exception(Exception::GENERAL_MOCK, 'Wrong file uploaded');
}
}
});
App::get('/v1/mock/tests/general/redirect')
->desc('Redirect')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirect')
->label('sdk.description', 'Mock a redirect request.')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response->redirect('/v1/mock/tests/general/redirect/done');
});
App::get('/v1/mock/tests/general/redirect/done')
->desc('Redirected')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirected')
->label('sdk.description', 'Mock a redirected request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->action(function () {
});
App::get('/v1/mock/tests/general/set-cookie')
->desc('Set Cookie')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'setCookie')
->label('sdk.description', 'Mock a set cookie request.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('response')
->inject('request')
->action(function (Response $response, Request $request) {
$response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', $request->getHostname(), true, true);
});
App::get('/v1/mock/tests/general/get-cookie')
->desc('Get Cookie')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'getCookie')
->label('sdk.description', 'Mock a cookie response.')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.mock', true)
->inject('request')
->action(function (Request $request) {
if ($request->getCookie('cookieName', '') !== 'cookieValue') {
throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value');
}
});
App::get('/v1/mock/tests/general/empty')
->desc('Empty Response')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'empty')
->label('sdk.description', 'Mock an empty response.')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response->noContent();
});
App::post('/v1/mock/tests/general/nullable')
->desc('Nullable Test')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'nullable')
->label('sdk.description', 'Mock a nullable parameter.')
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
->param('optional', '', new Text(100), 'Sample string param', true)
->action(function (string $required, string $nullable, ?string $optional) {
});
App::post('/v1/mock/tests/general/enum')
->desc('Enum Test')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'enum')
->label('sdk.description', 'Mock an enum parameter.')
->label('sdk.mock', true)
->param('mockType', '', new WhiteList(['first', 'second', 'third']), 'Sample enum param')
->action(function (string $mockType) {
});
App::get('/v1/mock/tests/general/400-error')
->desc('400 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error400')
->label('sdk.description', 'Mock a 400 failed request.')
->label('sdk.response.code', Response::STATUS_CODE_BAD_REQUEST)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ERROR)
->label('sdk.mock', true)
->action(function () {
throw new Exception(Exception::GENERAL_MOCK, 'Mock 400 error');
});
App::get('/v1/mock/tests/general/500-error')
->desc('500 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error500')
->label('sdk.description', 'Mock a 500 failed request.')
->label('sdk.response.code', Response::STATUS_CODE_INTERNAL_SERVER_ERROR)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ERROR)
->label('sdk.mock', true)
->action(function () {
throw new Exception(Exception::GENERAL_MOCK, 'Mock 500 error', 500);
});
App::get('/v1/mock/tests/general/502-error')
->desc('502 Error')
->groups(['mock'])
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'general')
->label('sdk.method', 'error502')
->label('sdk.description', 'Mock a 502 bad gateway.')
->label('sdk.response.code', Response::STATUS_CODE_BAD_GATEWAY)
->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk.mock', true)
->inject('response')
->action(function (Response $response) {
$response
->setStatusCode(502)
->text('This is a text error')
;
});
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
App::get('/v1/mock/tests/general/oauth2')
->desc('OAuth Login')
@@ -646,6 +153,66 @@ App::patch('/v1/mock/functions-v2')
$response->noContent();
});
App::get('/v1/mock/github/callback')
->desc('Create installation document using GitHub installation id')
->groups(['mock', 'api', 'vcs'])
->label('scope', 'public')
->label('docs', false)
->param('providerInstallationId', '', new UID(), 'GitHub installation ID')
->param('projectId', '', new UID(), 'Project ID of the project where app is to be installed')
->inject('gitHub')
->inject('project')
->inject('response')
->inject('dbForConsole')
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) {
$isDevelopment = App::getEnv('_APP_ENV', 'development') === 'development';
if (!$isDevelopment) {
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
}
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
$error = 'Project with the ID from state could not be found.';
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
}
if (!empty($providerInstallationId)) {
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$projectInternalId = $project->getInternalId();
$teamId = $project->getAttribute('teamId', '');
$installation = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'providerInstallationId' => $providerInstallationId,
'projectId' => $projectId,
'projectInternalId' => $projectInternalId,
'provider' => 'github',
'organization' => $owner,
'personal' => false
]);
$installation = $dbForConsole->createDocument('installations', $installation);
}
$response->json([
'installationId' => $installation->getId(),
]);
});
App::shutdown()
->groups(['mock'])
->inject('utopia')
+143 -73
View File
@@ -8,8 +8,8 @@ use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Extend\Exception;
use Appwrite\Event\Usage;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Usage\Stats;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Request;
use Utopia\App;
@@ -48,43 +48,95 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
return $label;
};
$databaseListener = function (string $event, Document $document, Stats $usage) {
$multiplier = 1;
$databaseListener = function (string $event, Document $document, Document $project, Usage $queueForUsage, Database $dbForProject) {
$value = 1;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$multiplier = -1;
$value = -1;
}
$collection = $document->getCollection();
switch ($collection) {
case 'users':
$usage->setParam('users.{scope}.count.total', 1 * $multiplier);
switch (true) {
case $document->getCollection() === 'teams':
$queueForUsage
->addMetric(METRIC_TEAMS, $value); // per project
break;
case 'databases':
$usage->setParam('databases.{scope}.count.total', 1 * $multiplier);
case $document->getCollection() === 'users':
$queueForUsage
->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
->addReduce($document);
}
break;
case 'buckets':
$usage->setParam('buckets.{scope}.count.total', 1 * $multiplier);
case $document->getCollection() === 'sessions': // sessions
$queueForUsage
->addMetric(METRIC_SESSIONS, $value); //per project
break;
case 'deployments':
$usage->setParam('deployments.{scope}.storage.size', $document->getAttribute('size') * $multiplier);
case $document->getCollection() === 'databases': // databases
$queueForUsage
->addMetric(METRIC_DATABASES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$queueForUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value) // per database
;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$queueForUsage
->addMetric(METRIC_DOCUMENTS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
break;
case $document->getCollection() === 'buckets': //buckets
$queueForUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$queueForUsage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$queueForUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$queueForUsage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_FUNCTION_ID_DEPLOYMENTS), $value)// per function
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
break;
default:
if (strpos($collection, 'bucket_') === 0) {
$usage
->setParam('bucketId', $document->getAttribute('bucketId'))
->setParam('files.{scope}.storage.size', $document->getAttribute('sizeOriginal') * $multiplier)
->setParam('files.{scope}.count.total', 1 * $multiplier);
} elseif (strpos($collection, 'database_') === 0) {
$usage
->setParam('databaseId', $document->getAttribute('databaseId'));
if (strpos($collection, '_collection_') !== false) {
$usage
->setParam('collectionId', $document->getAttribute('$collectionId'))
->setParam('documents.{scope}.count.total', 1 * $multiplier);
} else {
$usage->setParam('collections.{scope}.count.total', 1 * $multiplier);
}
}
break;
}
};
@@ -103,8 +155,8 @@ App::init()
->inject('dbForProject')
->inject('mode')
->inject('queueForMails')
->inject('usage')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Database $dbForProject, string $mode, Mail $queueForMails, Stats $usage) use ($databaseListener) {
->inject('queueForUsage')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Database $dbForProject, string $mode, Mail $queueForMails, Usage $queueForUsage) use ($databaseListener) {
$route = $utopia->getRoute();
@@ -121,13 +173,17 @@ App::init()
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($abuseKeyLabel as $abuseKey) {
$start = $request->getContentRangeStart();
$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())
->setParam('{url}', $request->getHostname() . $route->getPath())
->setParam('{method}', $request->getMethod());
->setParam('{method}', $request->getMethod())
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
$timeLimitArray[] = $timeLimit;
}
@@ -186,19 +242,14 @@ App::init()
->setProject($project)
->setUser($user);
$usage
->setParam('projectInternalId', $project->getInternalId())
->setParam('projectId', $project->getId())
->setParam('project.{scope}.network.requests', 1)
->setParam('httpMethod', $request->getMethod())
->setParam('project.{scope}.network.inbound', 0)
->setParam('project.{scope}.network.outbound', 0);
$queueForDeletes->setProject($project);
$queueForDatabase->setProject($project);
$dbForProject->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, Document $document) => $databaseListener($event, $document, $usage));
$dbForProject->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, Document $document) => $databaseListener($event, $document, $usage));
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject))
;
$useCache = $route->getLabel('cache', false);
@@ -285,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;
@@ -296,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');
@@ -309,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;
}
});
@@ -350,7 +407,7 @@ App::shutdown()
$session = array_shift($sessions);
$dbForProject->deleteDocument('sessions', $session->getId());
}
$dbForProject->deleteCachedDocument('users', $userId);
$dbForProject->purgeCachedDocument('users', $userId);
});
App::shutdown()
@@ -362,14 +419,14 @@ App::shutdown()
->inject('user')
->inject('queueForEvents')
->inject('queueForAudits')
->inject('usage')
->inject('queueForUsage')
->inject('queueForDeletes')
->inject('queueForDatabase')
->inject('dbForProject')
->inject('queueForFunctions')
->inject('mode')
->inject('dbForConsole')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Stats $usage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -522,35 +579,48 @@ App::shutdown()
}
}
if (
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
&& $project->getId()
&& !empty($route->getLabel('sdk.namespace', null))
) { // Don't calculate console usage on admin mode
$metric = $route->getLabel('usage.metric', '');
$usageParams = $route->getLabel('usage.params', []);
if (!empty($metric)) {
$usage->setParam($metric, 1);
foreach ($usageParams as $param) {
$param = $parseLabel($param, $responsePayload, $requestParams, $user);
$parts = explode(':', $param);
if (count($parts) != 2) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Usage params not properly set');
}
$usage->setParam($parts[0], $parts[1]);
if ($project->getId() !== 'console') {
if ($mode !== APP_MODE_ADMIN) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
}
$queueForUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize());
}
$queueForUsage
->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);
}
}
}
});
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
}
$usage
->setParam('project.{scope}.network.inbound', $request->getSize() + $fileSize)
->setParam('project.{scope}.network.outbound', $response->getSize())
->submit();
App::init()
->groups(['usage'])
->action(function () {
if (App::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
throw new Exception(Exception::GENERAL_USAGE_DISABLED);
}
});
+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);
}
+195 -165
View File
@@ -12,13 +12,14 @@ 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;
use Appwrite\Extend\Exception;
use Appwrite\Auth\Auth;
use Appwrite\Event\Audit;
@@ -32,8 +33,8 @@ use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Origin;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\URL\URL as AppwriteURL;
use Appwrite\Usage\Stats;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Logger\Logger;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
@@ -72,14 +73,17 @@ use Ahc\Jwt\JWTException;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
use Appwrite\Event\Func;
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;
@@ -109,8 +113,8 @@ 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 = 514;
const APP_VERSION_STABLE = '1.4.6';
const APP_CACHE_BUSTER = 331;
const APP_VERSION_STABLE = '1.4.13';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -139,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
@@ -173,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';
@@ -196,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';
@@ -242,6 +244,7 @@ Config::load('platforms', __DIR__ . '/config/platforms.php');
Config::load('collections', __DIR__ . '/config/collections.php');
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
Config::load('runtimes-v2', __DIR__ . '/config/runtimes-v2.php');
Config::load('usage', __DIR__ . '/config/usage.php');
Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
Config::load('services', __DIR__ . '/config/services.php'); // List of services
@@ -653,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)) {
@@ -683,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()) {
@@ -732,7 +725,7 @@ $register->set('pools', function () {
default => null
};
$adapter->setDefaultDatabase($dsn->getPath());
$adapter->setDatabase($dsn->getPath());
break;
case 'pubsub':
$adapter = $resource();
@@ -752,7 +745,6 @@ $register->set('pools', function () {
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
break;
}
return $adapter;
@@ -767,31 +759,26 @@ $register->set('pools', function () {
return $group;
});
$register->set('influxdb', 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', '');
// Register DB connection
$host = App::getEnv('_APP_INFLUXDB_HOST', '');
$port = App::getEnv('_APP_INFLUXDB_PORT', '');
$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,
));
if (empty($host) || empty($port)) {
return;
}
$driver = new InfluxDB\Driver\Curl("http://{$host}:{$port}");
$client = new InfluxDB\Client($host, $port, '', '', false, false, 5);
$client->setDriver($driver);
return $client;
return $pdo;
});
$register->set('statsd', function () {
// Register DB connection
$host = App::getEnv('_APP_STATSD_HOST', 'telegraf');
$port = App::getEnv('_APP_STATSD_PORT', 8125);
$connection = new \Domnikl\Statsd\Connection\UdpSocket($host, $port);
$statsd = new \Domnikl\Statsd\Client($connection);
return $statsd;
});
$register->set('smtp', function () {
$mail = new PHPMailer(true);
@@ -832,6 +819,9 @@ $register->set('passwordsDictionary', function () {
$register->set('promiseAdapter', function () {
return new Swoole();
});
$register->set('hooks', function () {
return new Hooks();
});
/*
* Localization
*/
@@ -867,13 +857,14 @@ foreach ($locales as $locale) {
]);
// Runtime Execution
App::setResource('log', fn() => new Log());
App::setResource('logger', function ($register) {
return $register->get('logger');
}, ['register']);
App::setResource('loggerBreadcrumbs', function () {
return [];
});
App::setResource('hooks', function ($register) {
return $register->get('hooks');
}, ['register']);
App::setResource('register', fn() => $register);
App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en')));
@@ -910,15 +901,15 @@ App::setResource('queueForAudits', function (Connection $queue) {
App::setResource('queueForFunctions', function (Connection $queue) {
return new Func($queue);
}, ['queue']);
App::setResource('queueForUsage', function (Connection $queue) {
return new Usage($queue);
}, ['queue']);
App::setResource('queueForCertificates', function (Connection $queue) {
return new Certificate($queue);
}, ['queue']);
App::setResource('queueForMigrations', function (Connection $queue) {
return new Migration($queue);
}, ['queue']);
App::setResource('usage', function ($register) {
return new Stats($register->get('statsd'));
}, ['register']);
App::setResource('clients', function ($request, $console, $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => ID::custom('platforms'),
@@ -927,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
@@ -935,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) {
@@ -1049,6 +1056,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
}
}
$dbForProject->setMetadata('user', $user->getId());
$dbForConsole->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
@@ -1121,23 +1131,37 @@ 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);
return $database;
//if ($project->getAttribute('database') === DATABASE_SHARED_TABLES) {
$database
->setShareTables(true)
->setTenant($project->getInternalId())
->setNamespace('');
// } else {
// $database
// ->setShareTables(false)
// ->setTenant(null)
// ->setNamespace('_' . $project->getInternalId());
// }
return null;
// return $database;
}, ['pools', 'dbForConsole', 'cache', 'project']);
App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
$dbAdapter = $pools
->get('console')
->pop()
->getResource()
;
->getResource();
$database = new Database($dbAdapter, $cache);
$database
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
return $database;
@@ -1146,20 +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;
}
@@ -1169,17 +1208,10 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$databaseName] = $database;
$database
->setNamespace('_' . $project->getInternalId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
$configure($database);
return $database;
};
return $getProjectDB;
}, ['pools', 'dbForConsole', 'cache']);
App::setResource('cache', function (Group $pools) {
@@ -1236,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),
};
}
}
+44 -25
View File
@@ -1,8 +1,10 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
@@ -20,7 +22,6 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Appwrite\Utopia\Request;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
@@ -28,6 +29,9 @@ use Utopia\Database\Database;
use Utopia\WebSocket\Server;
use Utopia\WebSocket\Adapter;
/**
* @var \Utopia\Registry\Registry $register
*/
require_once __DIR__ . '/init.php';
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
@@ -47,7 +51,10 @@ function getConsoleDB(): Database
$database = new Database($dbAdapter, getCache());
$database->setNamespace('_console');
$database
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', '_console');
return $database;
}
@@ -70,7 +77,18 @@ function getProjectDB(Document $project): Database
;
$database = new Database($dbAdapter, getCache());
$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;
}
@@ -122,12 +140,12 @@ $server = new Server($adapter);
$logError = function (Throwable $error, string $action) use ($register) {
$logger = $register->get('logger');
if ($logger) {
if ($logger && !$error instanceof Exception) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace("realtime");
$log->setServer(\gethostname());
$log->setServer(gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
@@ -182,7 +200,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
break;
} catch (\Throwable $th) {
} catch (Throwable) {
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
sleep(DATABASE_RECONNECT_SLEEP);
}
@@ -210,7 +228,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
->setAttribute('value', json_encode($payload));
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (\Throwable $th) {
} catch (Throwable $th) {
call_user_func($logError, $th, "updateWorkerDocument");
} finally {
$register->get('pools')->reclaim();
@@ -362,7 +380,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$stats->incr($event['project'], 'messages', $num);
}
});
} catch (\Throwable $th) {
} catch (Throwable $th) {
call_user_func($logError, $th, "pubSubConnection");
Console::error('Pub/sub error: ' . $th->getMessage());
@@ -389,19 +407,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
App::setResource('response', fn() => $response);
try {
/** @var \Utopia\Database\Document $project */
/** @var Document $project */
$project = $app->getResource('project');
/*
* Project Check
*/
if (empty($project->getId())) {
throw new Exception('Missing or unknown project ID', 1008);
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
}
$dbForProject = getProjectDB($project);
$console = $app->getResource('console'); /** @var \Utopia\Database\Document $console */
$user = $app->getResource('user'); /** @var \Utopia\Database\Document $user */
$console = $app->getResource('console'); /** @var Document $console */
$user = $app->getResource('user'); /** @var Document $user */
/*
* Abuse Check
@@ -416,7 +434,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$abuse = new Abuse($timeLimit);
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
throw new Exception('Too many requests', 1013);
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
}
/*
@@ -425,10 +443,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
* Skip this check for non-web platforms which are not required to send an origin header.
*/
$origin = $request->getOrigin();
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
$originValidator = new Origin(array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if (!$originValidator->isValid($origin) && $project->getId() !== 'console') {
throw new Exception($originValidator->getDescription(), 1008);
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
$roles = Auth::getRoles($user);
@@ -439,7 +457,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
* Channels Check
*/
if (empty($channels)) {
throw new Exception('Missing channels', 1008);
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
}
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
@@ -460,7 +478,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
]);
$stats->incr($project->getId(), 'connections');
$stats->incr($project->getId(), 'connectionsTotal');
} catch (\Throwable $th) {
} catch (Throwable $th) {
call_user_func($logError, $th, "initServer");
$response = [
@@ -486,7 +504,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try {
$app = new App('UTC');
$response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId'];
$database = getConsoleDB();
@@ -494,6 +511,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
if ($projectId !== 'console') {
$project = Authorization::skip(fn() => $database->getDocument('projects', $projectId));
$database = getProjectDB($project);
} else {
$project = null;
}
/*
@@ -510,22 +529,22 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$abuse = new Abuse($timeLimit);
if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
throw new Exception('Too many messages', 1013);
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many messages.');
}
$message = json_decode($message, true);
if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) {
throw new Exception('Message format is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message format is not valid.');
}
switch ($message['type']) {
/**
/**
* This type is used to authenticate.
*/
case 'authentication':
if (!array_key_exists('session', $message['data'])) {
throw new Exception('Payload is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
}
$session = Auth::decodeSession($message['data']['session']);
@@ -540,7 +559,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret, $authDuration) // Validate user has valid login token
) {
// cookie not valid
throw new Exception('Session is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
}
$roles = Auth::getRoles($user);
@@ -560,9 +579,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
break;
default:
throw new Exception('Message type is not valid.', 1003);
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
}
} catch (\Throwable $th) {
} catch (Throwable $th) {
$response = [
'type' => 'error',
'data' => [
+7 -36
View File
@@ -71,7 +71,6 @@ services:
- mariadb
- redis
# - clamav
- influxdb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -79,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
@@ -106,8 +106,6 @@ services:
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_USAGE_STATS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_STORAGE_ANTIVIRUS
@@ -144,9 +142,8 @@ services:
- _APP_EXECUTOR_HOST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STATSD_HOST
- _APP_STATSD_PORT
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_DELAY
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
@@ -273,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
@@ -420,7 +417,7 @@ services:
depends_on:
- redis
- mariadb
volumes:
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
environment:
@@ -595,16 +592,15 @@ services:
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
appwrite-usage:
appwrite-worker-usage:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: usage
container_name: appwrite-usage
container_name: appwrite-worker-usage
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- influxdb
- mariadb
environment:
- _APP_ENV
@@ -615,9 +611,6 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -652,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
@@ -751,27 +744,6 @@ services:
# volumes:
# - appwrite-uploads:/storage/uploads
influxdb:
image: appwrite/influxdb:1.5.0
container_name: appwrite-influxdb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-influxdb:/var/lib/influxdb:rw
telegraf:
image: appwrite/telegraf:1.4.0
container_name: appwrite-telegraf
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
environment:
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
networks:
gateway:
name: gateway
@@ -788,5 +760,4 @@ volumes:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-influxdb:
appwrite-config:
+91 -22
View File
@@ -9,12 +9,13 @@ 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\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Phone;
use Appwrite\Event\Usage;
use Appwrite\Event\UsageDump;
use Appwrite\Platform\Appwrite;
use Appwrite\Usage\Stats;
use Swoole\Runtime;
use Utopia\App;
use Utopia\Cache\Adapter\Sharding;
@@ -22,6 +23,7 @@ use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Service;
@@ -36,7 +38,6 @@ use Utopia\Queue\Connection;
Authorization::disable();
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
Server::setResource('register', fn () => $register);
Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
@@ -62,17 +63,44 @@ 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) {
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
if ($project->getId() === 'console') {
return $project;
}
return $dbForConsole->getDocument('projects', $project->getId());
;
}, ['message', 'dbForConsole']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
@@ -85,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;
}
@@ -98,12 +138,34 @@ 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;
};
}, ['pools', 'dbForConsole', 'cache']);
Server::setResource('abuseRetention', function () {
return DateTime::addSeconds(new \DateTime(), -1 * App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400));
});
Server::setResource('auditRetention', function () {
return DateTime::addSeconds(new \DateTime(), -1 * App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600));
});
Server::setResource('executionRetention', function () {
return DateTime::addSeconds(new \DateTime(), -1 * App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600));
});
Server::setResource('cache', function (Registry $register) {
$pools = $register->get('pools');
$list = Config::getParam('pools-cache', []);
@@ -120,9 +182,12 @@ Server::setResource('cache', function (Registry $register) {
return new Cache(new Sharding($adapters));
}, ['register']);
Server::setResource('log', fn() => new Log());
Server::setResource('usage', function ($register) {
return new Stats($register->get('statsd'));
}, ['register']);
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']);
@@ -156,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']);
@@ -205,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');
@@ -223,7 +291,6 @@ try {
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
}
$worker = $platform->getWorker();
$worker
@@ -238,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) {
@@ -254,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());
@@ -273,9 +342,9 @@ $worker
Console::error('[Error] Line: ' . $error->getLine());
});
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->start();
$worker->start();
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php create-inf-metric $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php delete-orphaned-projects $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php get-migration-stats $@
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php patch-recreate-repositories-documents $@
+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 $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php usage-dump $@
+13 -14
View File
@@ -1,4 +1,5 @@
{
"name": "appwrite/server-ce",
"description": "End to end backend server for frontend and mobile apps.",
"type": "project",
@@ -43,39 +44,37 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.13.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.32.*",
"utopia-php/abuse": "0.34.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.34.*",
"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.44.*",
"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.1.*",
"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.5.*",
"utopia-php/queue": "0.7.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.17.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.5.*",
"utopia-php/vcs": "0.5.*",
"utopia-php/vcs": "0.6.*",
"utopia-php/websocket": "0.1.*",
"matomo/device-detector": "6.1.*",
"dragonmantank/cron-expression": "3.3.2",
"influxdb/influxdb-php": "1.15.2",
"phpmailer/phpmailer": "6.8.0",
"chillerlan/php-qrcode": "4.3.4",
"adhocore/jwt": "1.1.2",
"webonyx/graphql-php": "14.11.*",
"slickdeals/statsd": "3.1.0",
"league/csv": "9.7.1"
},
"repositories": [
@@ -86,7 +85,7 @@
],
"require-dev": {
"ext-fileinfo": "*",
"appwrite/sdk-generator": "0.35.*",
"appwrite/sdk-generator": "0.36.*",
"phpunit/phpunit": "9.5.20",
"squizlabs/php_codesniffer": "^3.7",
"swoole/ide-helper": "5.0.2",
Generated
+296 -1058
View File
File diff suppressed because it is too large Load Diff
+122 -87
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
@@ -127,8 +128,6 @@ services:
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_USAGE_STATS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_STORAGE_ANTIVIRUS
@@ -165,8 +164,6 @@ services:
- _APP_EXECUTOR_HOST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STATSD_HOST
- _APP_STATSD_PORT
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_CACHE
@@ -195,7 +192,7 @@ services:
container_name: appwrite-realtime
image: appwrite-dev
restart: unless-stopped
ports:
ports:
- 9505:80
labels:
- "traefik.enable=true"
@@ -303,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
@@ -357,7 +354,7 @@ services:
image: appwrite-dev
networks:
- appwrite
volumes:
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
@@ -456,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
@@ -574,6 +571,7 @@ services:
- _APP_REDIS_PASS
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_SMS_PROJECTS_DENY_LIST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@@ -647,20 +645,20 @@ services:
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_MAINTENANCE_DELAY
appwrite-usage:
entrypoint: usage
appwrite-worker-usage:
entrypoint: worker-usage
<<: *x-logging
container_name: appwrite-usage
container_name: appwrite-worker-usage
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- ./dev:/usr/local/dev
depends_on:
- influxdb
- redis
- mariadb
environment:
- _APP_ENV
@@ -671,9 +669,6 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -681,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
@@ -711,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
@@ -805,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,26 +927,6 @@ services:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
influxdb:
image: appwrite/influxdb:1.5.0
container_name: appwrite-influxdb
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-influxdb:/var/lib/influxdb:rw
telegraf:
image: appwrite/telegraf:1.4.0
container_name: appwrite-telegraf
<<: *x-logging
networks:
- appwrite
environment:
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
# Dev Tools Start ------------------------------------------------------------------------------------------
#
# The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack
@@ -871,16 +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
# Chronograf - A nice UI for exploring InfluxDB 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
@@ -889,7 +953,7 @@ services:
container_name: appwrite-requestcatcher
<<: *x-logging
ports:
- '9504:5000'
- "9504:5000"
networks:
- appwrite
@@ -903,42 +967,16 @@ services:
networks:
- appwrite
# redis-commander:
# image: rediscommander/redis-commander:latest
# restart: unless-stopped
# networks:
# - appwrite
# environment:
# - REDIS_HOSTS=redis
# ports:
# - "8081:8081"
redis-insight:
image: redis/redisinsight:latest
restart: unless-stopped
networks:
- appwrite
environment:
- REDIS_HOSTS=redis
ports:
- "8081:5540"
# chronograf:
# image: chronograf:1.6
# container_name: appwrite-chronograf
# restart: unless-stopped
# networks:
# - appwrite
# volumes:
# - appwrite-chronograf:/var/lib/chronograf
# ports:
# - "8888:8888"
# environment:
# - INFLUXDB_URL=http://influxdb:8086
# - KAPACITOR_URL=http://kapacitor:9092
# - AUTH_DURATION=48h
# - TOKEN_SECRET=duperduper5674829!jwt
# - GH_CLIENT_ID=d86f7145a41eacfc52cc
# - GH_CLIENT_SECRET=9e0081062367a2134e7f2ea95ba1a32d08b6c8ab
# - GH_ORGS=appwrite
# webgrind:
# image: 'jokkedk/webgrind:latest'
# volumes:
# - './debug:/tmp'
# ports:
# - '3001:80'
graphql-explorer:
container_name: appwrite-graphql-explorer
image: appwrite/altair:0.3.0
@@ -950,7 +988,6 @@ services:
environment:
- SERVER_URL=http://localhost/v1/graphql
# Dev Tools End ------------------------------------------------------------------------------------------
networks:
@@ -969,6 +1006,4 @@ volumes:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-influxdb:
appwrite-config:
# appwrite-chronograf:
@@ -1 +1,2 @@
appwrite health getQueueBuilds
appwrite health getQueueBuilds \
@@ -1 +1,2 @@
appwrite health getQueueCertificates
appwrite health getQueueCertificates \
@@ -1,2 +1,3 @@
appwrite health getQueueDatabases \
@@ -1 +1,2 @@
appwrite health getQueueDeletes
appwrite health getQueueDeletes \
@@ -1 +1,2 @@
appwrite health getQueueFunctions
appwrite health getQueueFunctions \
@@ -1 +1,2 @@
appwrite health getQueueLogs
appwrite health getQueueLogs \
@@ -1 +1,2 @@
appwrite health getQueueMails
appwrite health getQueueMails \
@@ -1 +1,2 @@
appwrite health getQueueMessaging
appwrite health getQueueMessaging \
@@ -1 +1,2 @@
appwrite health getQueueMigrations
appwrite health getQueueMigrations \
@@ -1 +1,2 @@
appwrite health getQueueWebhooks
appwrite health getQueueWebhooks \
@@ -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

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