Compare commits

...
Author SHA1 Message Date
shimon 87f41f5ceb sync with 1.6.x 2025-03-06 13:14:09 +02:00
Christy JacobandGitHub 205024657a Merge pull request #9438 from appwrite/activities-multi-writes
Activities batch writes
2025-03-06 11:40:34 +05:30
Steven NguyenandGitHub fab43a5f83 Merge pull request #9447 from appwrite/pla-2607
Fix blocking of phone sessions when disabled on console
2025-03-05 11:13:40 -08:00
Chirag Aggarwal 36b0475298 chore: update tests 2025-03-05 18:40:41 +00:00
Chirag Aggarwal 0ce4555f70 chore: added auth group to create phone token 2025-03-05 17:42:43 +00:00
Christy JacobandGitHub a734cadba1 Merge pull request #9443 from appwrite/feat-database-storage-in-stats-resources
Feat: calculate database storage in stats-resources
2025-03-05 12:06:56 +05:30
Damodar Lohani 2afb8a14ac Skip in dual writing 2025-03-05 05:27:56 +00:00
Damodar Lohani 6024d01e7b Feat: calculate database storage in stats-resources
- Calculate database storage in stats-resources worker
2025-03-05 05:25:50 +00:00
Christy JacobandGitHub f250f454f7 Merge pull request #9442 from appwrite/fix-model-image-trans
fix: model for image transformations in usage project
2025-03-05 10:21:05 +05:30
Chirag Aggarwal 2c6f731a38 fix: description 2025-03-05 04:49:34 +00:00
Chirag Aggarwal 1d2883f472 chore: fix model for image transformations in usage project 2025-03-05 04:46:06 +00:00
Jake BarnbyandGitHub 8c1a1604a8 Merge pull request #9387 from appwrite/pla-2526
chore: make min/max params optional for attribute update
2025-03-05 16:13:59 +13:00
Jake BarnbyandGitHub ea2fd4b995 Merge pull request #9426 from appwrite/pla-2584
chore: added timeout to deployment builds in tests
2025-03-05 16:11:47 +13:00
shimon f2ad3a501e updating audits workers var scope 2025-03-04 20:34:56 +02:00
shimon c443c8fdab updating audits workers var scope 2025-03-04 19:49:37 +02:00
shimon 1838b9c4e8 updating audits workers var scope 2025-03-04 19:49:10 +02:00
shimon 09aa383cf3 updating audits workers var scope 2025-03-04 19:15:00 +02:00
shimon ce054e7088 updating audits workers var scope 2025-03-04 19:12:20 +02:00
shimon 76dc8c29d5 updating audits workers var scope 2025-03-04 19:07:45 +02:00
shimon f761829d60 updating audits workers var scope 2025-03-04 18:49:31 +02:00
shimon 8b4bc7e4a6 updating audits workers var scope 2025-03-04 18:19:23 +02:00
Christy JacobandGitHub 52ba87e77e Merge pull request #9393 from appwrite/add-file-transformation-endpoints
chore: add image transformation stats to usage endpoint
2025-03-04 14:46:14 +05:30
Chirag Aggarwal 239bebfe2e Merge branch '1.6.x' into add-file-transformation-endpoints 2025-03-04 07:34:43 +00:00
Christy JacobandGitHub eff58459ef Merge pull request #9425 from appwrite/disable-admin-transformedAt
Disable transformedAt update for console users
2025-03-04 12:21:28 +05:30
Christy JacobandGitHub 85456696b8 Merge pull request #9429 from appwrite/fix-dual-writing
Fix: disable dual writing
2025-03-04 11:55:00 +05:30
Damodar Lohani 0659dce935 Fix: disable dual writing 2025-03-04 06:22:58 +00:00
Christy JacobandGitHub d5ab989891 Merge pull request #9339 from appwrite/feat-remove-deprecated-usage-stuff
Remove usage and usage dump in favor of stats-usage and stats-usage-dump
2025-03-04 11:23:29 +05:30
Christy JacobandGitHub 9b18a031d9 Merge pull request #9428 from appwrite/fix-stats-date
Fix: use receivedAt date when available
2025-03-04 11:22:12 +05:30
Damodar Lohani 4d5d30ba7b Fix default value 2025-03-04 05:50:21 +00:00
Damodar Lohani dd5dd4cc43 Same for database metrics 2025-03-04 05:34:02 +00:00
Damodar Lohani 5d61544444 Fix: use receivedAt date when available 2025-03-04 05:31:35 +00:00
Chirag Aggarwal 9a6de87f7d chore: usagetest and migrationsbase 2025-03-03 16:31:41 +00:00
Chirag Aggarwal fe7aeefabf chore: remove redundant param 2025-03-03 16:20:32 +00:00
Chirag Aggarwal fc05a4f1e3 chore: updated to use assertEventually 2025-03-03 16:07:33 +00:00
Chirag Aggarwal e865ca74d7 chore: added timeout to webhooks base test 2025-03-03 14:41:11 +00:00
Chirag Aggarwal c6b8e649fe chore: added maxtimeout to testChannelExecutions 2025-03-03 14:34:10 +00:00
shimon b314ae8208 disable transformedAt update for console users 2025-03-03 15:19:49 +02:00
Christy JacobandGitHub 32329ef3f9 Merge pull request #9424 from appwrite/revert-9411-lohanidamodar-patch-2
Revert "Fix: stats resources only queue projects accessed in last 3 hours"
2025-03-03 16:25:34 +05:30
Damodar LohaniandGitHub 5becd0a5dc Revert "Fix: stats resources only queue projects accessed in last 3 hours" 2025-03-03 16:04:31 +05:45
Jake BarnbyandGitHub d85b28aabc Merge pull request #9325 from appwrite/pla-1883
refactor: migrate Realtime::send calls to queueForRealtime
2025-03-03 21:23:55 +13:00
Chirag AggarwalandGitHub 88c8d2aa4c Merge branch '1.6.x' into pla-1883 2025-03-03 13:37:05 +05:30
Chirag Aggarwal 9849e7b232 Merge branch '1.6.x' into add-file-transformation-endpoints 2025-03-01 19:41:25 +00:00
Chirag Aggarwal 6900b717da chore: updated to use logsdb 2025-03-01 17:45:20 +00:00
Chirag Aggarwal aaee99e08b chore: generate specs 2025-03-01 17:19:55 +00:00
Chirag Aggarwal 712c33064a chore: fix sending of transformation stats 2025-03-01 17:18:44 +00:00
Chirag Aggarwal 02de0d2432 chore: fix stats count 2025-02-28 13:29:32 +00:00
Chirag Aggarwal df37462716 chore: fix typing and tests 2025-02-28 13:20:55 +00:00
Chirag Aggarwal d5cfdc570f chore: updated specs 2025-02-28 08:41:53 +00:00
Chirag Aggarwal e9d8d13fbd chore: added imageTransformations to project usage 2025-02-28 08:28:21 +00:00
Chirag Aggarwal f31acd214e chore: remove plan blocking 2025-02-27 09:52:27 +00:00
Chirag Aggarwal 388affa3d0 chore: updated naming for subscribers 2025-02-27 07:23:13 +00:00
Chirag Aggarwal c79ed286b1 chore: update tests 2025-02-26 17:35:06 +00:00
Chirag Aggarwal 28896081d7 chore: fix forloop 2025-02-26 16:50:12 +00:00
Chirag Aggarwal 7687784515 chore: added fallback to old values in update 2025-02-26 16:41:26 +00:00
Chirag Aggarwal 0ddcb9b14c chore: refactor adapter 2025-02-26 16:24:05 +00:00
Christy JacobandGitHub 1c8ec7ba1f Merge pull request #9406 from appwrite/bump-base-image
chore: bump docker-base
2025-02-26 18:49:25 +05:30
Christy JacobandGitHub e7eedcdeb7 Merge pull request #9397 from appwrite/track-options-requests
Track options requests
2025-02-26 18:48:50 +05:30
Chirag Aggarwal bb5ed1ccc6 chore: bump base to 0.10.1 2025-02-26 10:14:32 +00:00
Chirag Aggarwal ae78e793dd chore: added blocking to file preview endpoint 2025-02-26 08:52:06 +00:00
Christy JacobandGitHub 24613c2e9f Merge pull request #9243 from appwrite/fix-smtp
Fix: email (smtp) to multiple recipients
2025-02-26 12:57:23 +05:30
Christy JacobandGitHub 718e44adce Merge branch '1.6.x' into track-options-requests 2025-02-26 10:40:10 +05:30
Damodar LohaniandGitHub 1816fae5a3 Merge pull request #9411 from appwrite/lohanidamodar-patch-2
Fix: stats resources only queue projects accessed in last 3 hours
2025-02-26 09:05:22 +05:45
Damodar LohaniandGitHub 7ea108f015 Fix: stats resources only queue projects accessed in last 3 hours 2025-02-26 08:37:36 +05:45
Jake BarnbyandGitHub 69c00d428a Merge pull request #9410 from appwrite/batch-per-project
Batch per project
2025-02-26 14:02:24 +13:00
Jake BarnbyandGitHub e998474b97 Merge branch '1.6.x' into batch-per-project 2025-02-26 14:02:01 +13:00
Jake Barnby 6e82bfe8e7 Update date 2025-02-26 14:00:16 +13:00
Jake Barnby 34d91a6518 Revert "Test increase batch"
This reverts commit 7dfa3193c9.
2025-02-26 13:51:41 +13:00
Jake Barnby 7dfa3193c9 Test increase batch 2025-02-26 13:45:49 +13:00
Chirag Aggarwal 542e34bc94 chore: bump docker-base 2025-02-25 12:28:05 +00:00
Chirag Aggarwal 9f7ba3d4ca Merge branch '1.6.x' into fix-smtp 2025-02-25 09:48:21 +00:00
Jake BarnbyandGitHub 66dc3adb98 Merge pull request #9403 from appwrite/batch-per-project
Log batches per project
2025-02-25 21:46:33 +13:00
Jake Barnby 3a2babc906 Update date 2025-02-25 21:34:46 +13:00
Jake Barnby 03f0e382f4 Fix dates 2025-02-25 21:03:37 +13:00
Jake Barnby b272a8b1e7 Pass queries 2025-02-25 20:26:00 +13:00
Chirag Aggarwal 676431c355 chore: shifted to logsDb 2025-02-25 07:16:42 +00:00
Jake Barnby 2924f408b3 Reapply "Remove blocks"
This reverts commit f49346485a.
2025-02-25 19:21:35 +13:00
Jake Barnby b0547015a9 Add temp log date skip 2025-02-25 18:51:11 +13:00
Jake Barnby f49346485a Revert "Remove blocks"
This reverts commit 6f854b5eb6.
2025-02-25 18:39:12 +13:00
Jake Barnby 6f854b5eb6 Remove blocks 2025-02-25 18:18:25 +13:00
Chirag Aggarwal ab6c5c9f8d chore: bump utopia messaging 2025-02-25 04:47:35 +00:00
Chirag Aggarwal 7ac4a5bf27 chore: update naming 2025-02-25 04:43:44 +00:00
Jake Barnby 1f560d896f Fix plural 2025-02-25 16:58:30 +13:00
Jake Barnby 5efd231861 Allow validator to read logs collections from base 2025-02-25 16:56:13 +13:00
Jake Barnby 3f8e1cbedb Fix loop unset 2025-02-25 15:57:04 +13:00
Jake Barnby 08f4ed2b50 Log batches per project 2025-02-25 15:50:10 +13:00
Eldad A. FuxandGitHub 392b33ef26 Merge pull request #9398 from appwrite/disable-logs-display
disable logs display
2025-02-23 20:08:55 +01:00
shimon 6bd14d548f composer 2025-02-23 20:58:02 +02:00
shimon 06d22f910c disable logs display 2025-02-23 20:51:38 +02:00
Christy Jacob 8958070327 chore: linter 2025-02-23 15:48:33 +05:30
Christy Jacob 3f50532c91 chore: remove sleep in functions tests 2025-02-23 15:42:53 +05:30
Christy Jacob 4c49272389 chore: enable assistant 2025-02-23 15:40:54 +05:30
Christy Jacob f25df67002 chore: remove sleep() for usage stats, since batch size is now 1 2025-02-23 15:39:37 +05:30
Christy Jacob 94108e6f3f chore: remove sleep() for usage stats, since batch size is now 1 2025-02-23 15:23:34 +05:30
Christy Jacob f682e4ac6f chore: remoove sleep() for usage stats, since batch size is now 1 2025-02-23 14:56:15 +05:30
Christy Jacob cf41d44de4 fix: general tests 2025-02-23 14:47:57 +05:30
Christy Jacob 30327f86fd feat: enable assistant 2025-02-23 11:39:18 +05:30
Christy Jacob 2359c1adb6 feat: enable assistant 2025-02-23 11:38:59 +05:30
Christy Jacob 12b494982b feat: compute usage for OPTIONS and 4XX errors 2025-02-23 11:37:54 +05:30
Christy Jacob 65dd5fe9b3 feat: compute usage for OPTIONS and 4XX errors 2025-02-23 11:34:28 +05:30
Chirag Aggarwal 0e0bab4c40 chore: updated test 2025-02-21 13:28:49 +00:00
Chirag Aggarwal 4ed4c660d5 chore: added tests for file transformations 2025-02-21 12:53:17 +00:00
Chirag Aggarwal b1629b24ad chore: added file transformation stats to usage endpoint for bucket 2025-02-21 11:44:04 +00:00
Christy JacobandGitHub 9fa13ddfd8 Merge pull request #9391 from appwrite/fix-null-error-on-invite
chore: fix null errors on team invite
2025-02-21 16:09:43 +05:30
Chirag Aggarwal d6c2dfb915 chore: updated name 2025-02-21 03:27:46 +00:00
Steven NguyenandGitHub f402262ffc Merge pull request #9390 from appwrite/pla-2142
chore: bump utopia-php/image to 0.8.0
2025-02-20 10:44:54 -08:00
Chirag Aggarwal 9d9e44a14f chore: remove redundant check 2025-02-20 17:36:42 +00:00
Chirag Aggarwal 397a6a34ed chore: fix null errors on team invite 2025-02-20 17:30:26 +00:00
Chirag Aggarwal c660b9d858 chore: fix tests 2025-02-20 14:56:23 +00:00
Chirag Aggarwal f9cc7882aa chore: fix tests 2025-02-20 14:43:21 +00:00
Chirag Aggarwal 4e71e85b5b chore: bump utopia-php/image version 2025-02-20 14:32:34 +00:00
Chirag Aggarwal 077fe10d80 chore: fix tests 2025-02-20 14:12:14 +00:00
Chirag Aggarwal b0ced9b351 chore: updated tests 2025-02-20 13:37:13 +00:00
Christy JacobandGitHub 6cbcd695d1 Merge pull request #9388 from appwrite/fix-audits-v2
Fix removed audits for shared v2
2025-02-20 18:05:25 +05:30
Jake Barnby b6166063db Add back remove shared v2 2025-02-21 00:51:29 +13:00
Chirag Aggarwal ccb7dfa839 chore: added test cases 2025-02-20 11:15:05 +00:00
Chirag Aggarwal 6235a1c729 chore: make min/max params optional for attribute update 2025-02-20 10:33:58 +00:00
Christy JacobandGitHub 7319b12b51 Merge pull request #9353 from appwrite/feat-messaging-metrics
Feat messaging metrics
2025-02-20 12:25:14 +05:30
Chirag Aggarwal 55e7633bd1 chore: shift targets to realtime 2025-02-18 14:04:59 +00:00
Chirag Aggarwal 88a6616cf8 chore: fix naming 2025-02-18 13:05:50 +00:00
Chirag Aggarwal 9f7aaf7029 chore: fix warning in database worker 2025-02-18 12:51:19 +00:00
Chirag Aggarwal 235a357ca3 chore: initialized queueForWebhooks in worker init 2025-02-18 11:37:36 +00:00
Chirag AggarwalandGitHub b99d2c3c8c Merge branch '1.6.x' into pla-1883 2025-02-18 16:31:41 +05:30
Chirag Aggarwal a74c6e0b10 chore: added targets to realtime, refactor webhook queues 2025-02-18 07:58:56 +00:00
Chirag Aggarwal 6ef4e76aa6 Merge branch '1.6.x' into pla-1883 2025-02-17 18:54:41 +00:00
Chirag Aggarwal 227f2c476d Merge branch '1.6.x' into pla-1883 2025-02-17 18:46:05 +00:00
Chirag Aggarwal 4c7917687d Merge branch '1.6.x' into pla-1883 2025-02-14 07:16:43 +00:00
Damodar Lohani 1e446948e8 Fix error 2025-02-13 05:24:00 +00:00
Damodar Lohani 503b8a0cde spacing 2025-02-13 04:58:14 +00:00
Damodar Lohani 745947621f Feat: count for targets 2025-02-13 04:54:29 +00:00
Damodar Lohani 721b9b9aea remove old events 2025-02-13 00:52:20 +00:00
Damodar LohaniandGitHub 29ffd703db Merge branch '1.6.x' into feat-remove-deprecated-usage-stuff 2025-02-13 06:28:48 +05:45
Damodar Lohani f2fc45da02 fix format 2025-02-11 09:46:00 +00:00
Damodar LohaniandGitHub 2b2be60e77 Merge branch '1.6.x' into feat-remove-deprecated-usage-stuff 2025-02-11 15:20:23 +05:45
Damodar Lohani 940f4d12af Remove usage and usage dump in favor of stats-usage and stats-usage-dump 2025-02-11 07:52:37 +00:00
Chirag Aggarwal 5076303c33 chore: refactor realtime queueing in builds worker 2025-02-11 06:06:43 +00:00
Chirag Aggarwal 532160705f chore: refactor realtime queueing in functions worker 2025-02-11 05:50:30 +00:00
Chirag Aggarwal f666e18154 chore: fix databases worker realtime queueing 2025-02-11 05:00:45 +00:00
Chirag Aggarwal bcdae7f4e3 chore: refactor certificate realtime, fix database 2025-02-11 04:21:36 +00:00
Chirag Aggarwal 47fbb777ed chore: refactor migrations realtime queue 2025-02-11 04:03:02 +00:00
Chirag Aggarwal 2c4c42de05 docs: fix database worker 2025-02-11 03:53:18 +00:00
Chirag Aggarwal 375f643214 refactor: triggering realtime events with queueForRealtime 2025-02-11 03:29:32 +00:00
50 changed files with 1414 additions and 2220 deletions
+2 -4
View File
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.9.5 AS final
FROM appwrite/base:0.10.1 AS final
LABEL maintainer="team@appwrite.io"
@@ -88,9 +88,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-stats-usage && \
chmod +x /usr/local/bin/worker-stats-usage-dump && \
chmod +x /usr/local/bin/stats-resources && \
chmod +x /usr/local/bin/worker-stats-resources && \
chmod +x /usr/local/bin/worker-usage && \
chmod +x /usr/local/bin/worker-usage-dump
chmod +x /usr/local/bin/worker-stats-resources
# Letsencrypt Permissions
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
@@ -3383,7 +3383,7 @@
"parameters": [
{
"name": "code",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3404,7 +3404,8 @@
"union-china-pay",
"visa",
"mir",
"maestro"
"maestro",
"rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3423,7 +3424,8 @@
"Union China Pay",
"Visa",
"MIR",
"Maestro"
"Maestro",
"Rupay"
]
},
"in": "path"
@@ -4365,7 +4367,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
+131 -85
View File
@@ -3387,7 +3387,7 @@
"parameters": [
{
"name": "code",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3408,7 +3408,8 @@
"union-china-pay",
"visa",
"mir",
"maestro"
"maestro",
"rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3427,7 +3428,8 @@
"Union China Pay",
"Visa",
"MIR",
"Maestro"
"Maestro",
"Rupay"
]
},
"in": "path"
@@ -7823,7 +7825,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -11585,7 +11587,7 @@
},
"x-appwrite": {
"method": "getCertificate",
"weight": 134,
"weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11692,7 +11694,7 @@
},
"x-appwrite": {
"method": "getPubSub",
"weight": 130,
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11718,54 +11720,6 @@
]
}
},
"\/health\/queue": {
"get": {
"summary": "Get queue",
"operationId": "healthGetQueue",
"tags": [
"health"
],
"description": "Check the Appwrite queue messaging servers are up and connection is successful.",
"responses": {
"200": {
"description": "Health Status",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/healthStatus"
}
}
}
}
},
"x-appwrite": {
"method": "getQueue",
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Key": []
}
]
}
},
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -11788,7 +11742,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
"weight": 136,
"weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11849,7 +11803,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
"weight": 135,
"weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11910,7 +11864,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
"weight": 137,
"weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11982,7 +11936,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
"weight": 138,
"weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12081,8 +12035,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
"v1-usage",
"v1-usage-dump",
"v1-stats-resources",
"v1-stats-usage",
"v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -12130,7 +12085,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
"weight": 142,
"weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12191,7 +12146,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
"weight": 133,
"weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12252,7 +12207,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
"weight": 139,
"weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12313,7 +12268,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
"weight": 140,
"weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12374,7 +12329,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
"weight": 141,
"weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12413,14 +12368,14 @@
]
}
},
"\/health\/queue\/usage": {
"\/health\/queue\/stats-resources": {
"get": {
"summary": "Get usage queue",
"operationId": "healthGetQueueUsage",
"summary": "Get stats resources queue",
"operationId": "healthGetQueueStatsResources",
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -12434,13 +12389,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"method": "getQueueStatsResources",
"weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
"demo": "health\/get-queue-stats-resources.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12474,10 +12429,71 @@
]
}
},
"\/health\/queue\/usage-dump": {
"\/health\/queue\/stats-usage": {
"get": {
"summary": "Get stats usage queue",
"operationId": "healthGetQueueUsage",
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"responses": {
"200": {
"description": "Health Queue",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/healthQueue"
}
}
}
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "threshold",
"description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
"required": false,
"schema": {
"type": "integer",
"format": "int32",
"default": 5000
},
"in": "query"
}
]
}
},
"\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
"operationId": "healthGetQueueUsageDump",
"operationId": "healthGetQueueStatsUsageDump",
"tags": [
"health"
],
@@ -12495,13 +12511,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsageDump",
"method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
"demo": "health\/get-queue-stats-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12557,7 +12573,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
"weight": 132,
"weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12714,7 +12730,7 @@
},
"x-appwrite": {
"method": "getTime",
"weight": 131,
"weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -17609,17 +17625,17 @@
},
"endpoint": {
"type": "string",
"description": "Source's Appwrite Endpoint",
"description": "Source Appwrite endpoint",
"x-example": "https:\/\/example.com"
},
"projectId": {
"type": "string",
"description": "Source's Project ID",
"description": "Source Project ID",
"x-example": "<PROJECT_ID>"
},
"apiKey": {
"type": "string",
"description": "Source's API Key",
"description": "Source API Key",
"x-example": "<API_KEY>"
}
},
@@ -36052,6 +36068,20 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
},
"imageTransformations": {
"type": "array",
"description": "Aggregated number of files transformations per period.",
"items": {
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
},
"imageTransformationsTotal": {
"type": "integer",
"description": "Total aggregated number of files transformations.",
"x-example": 0,
"format": "int32"
}
},
"required": [
@@ -36059,7 +36089,9 @@
"filesTotal",
"filesStorageTotal",
"files",
"storage"
"storage",
"imageTransformations",
"imageTransformationsTotal"
]
},
"usageFunctions": {
@@ -36597,6 +36629,18 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
},
"imageTransformationsTotal": {
"type": "integer",
"description": "An array of aggregated number of image transformations.",
"x-example": 0,
"format": "int32"
},
"imageTransformations": {
"type": "integer",
"description": "Total aggregated number of image transformations.",
"x-example": 0,
"format": "int32"
}
},
"required": [
@@ -36628,7 +36672,9 @@
"authPhoneEstimate",
"authPhoneCountryBreakdown",
"databasesReads",
"databasesWrites"
"databasesWrites",
"imageTransformationsTotal",
"imageTransformations"
]
},
"headers": {
+97 -81
View File
@@ -3077,7 +3077,7 @@
"parameters": [
{
"name": "code",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3098,7 +3098,8 @@
"union-china-pay",
"visa",
"mir",
"maestro"
"maestro",
"rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3117,7 +3118,8 @@
"Union China Pay",
"Visa",
"MIR",
"Maestro"
"Maestro",
"Rupay"
]
},
"in": "path"
@@ -7381,7 +7383,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -10461,7 +10463,7 @@
},
"x-appwrite": {
"method": "getCertificate",
"weight": 134,
"weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10570,7 +10572,7 @@
},
"x-appwrite": {
"method": "getPubSub",
"weight": 130,
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10597,55 +10599,6 @@
]
}
},
"\/health\/queue": {
"get": {
"summary": "Get queue",
"operationId": "healthGetQueue",
"tags": [
"health"
],
"description": "Check the Appwrite queue messaging servers are up and connection is successful.",
"responses": {
"200": {
"description": "Health Status",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/healthStatus"
}
}
}
}
},
"x-appwrite": {
"method": "getQueue",
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
]
}
},
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -10668,7 +10621,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
"weight": 136,
"weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10730,7 +10683,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
"weight": 135,
"weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10792,7 +10745,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
"weight": 137,
"weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10865,7 +10818,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
"weight": 138,
"weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10966,8 +10919,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
"v1-usage",
"v1-usage-dump",
"v1-stats-resources",
"v1-stats-usage",
"v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -11015,7 +10969,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
"weight": 142,
"weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11077,7 +11031,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
"weight": 133,
"weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11139,7 +11093,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
"weight": 139,
"weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11201,7 +11155,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
"weight": 140,
"weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11263,7 +11217,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
"weight": 141,
"weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11303,14 +11257,14 @@
]
}
},
"\/health\/queue\/usage": {
"\/health\/queue\/stats-resources": {
"get": {
"summary": "Get usage queue",
"operationId": "healthGetQueueUsage",
"summary": "Get stats resources queue",
"operationId": "healthGetQueueStatsResources",
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -11324,13 +11278,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"method": "getQueueStatsResources",
"weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
"demo": "health\/get-queue-stats-resources.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11365,10 +11319,72 @@
]
}
},
"\/health\/queue\/usage-dump": {
"\/health\/queue\/stats-usage": {
"get": {
"summary": "Get stats usage queue",
"operationId": "healthGetQueueUsage",
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"responses": {
"200": {
"description": "Health Queue",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/healthQueue"
}
}
}
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "threshold",
"description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
"required": false,
"schema": {
"type": "integer",
"format": "int32",
"default": 5000
},
"in": "query"
}
]
}
},
"\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
"operationId": "healthGetQueueUsageDump",
"operationId": "healthGetQueueStatsUsageDump",
"tags": [
"health"
],
@@ -11386,13 +11402,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsageDump",
"method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
"demo": "health\/get-queue-stats-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11449,7 +11465,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
"weight": 132,
"weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11609,7 +11625,7 @@
},
"x-appwrite": {
"method": "getTime",
"weight": 131,
"weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
+6 -4
View File
@@ -3557,7 +3557,7 @@
"parameters": [
{
"name": "code",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3577,7 +3577,8 @@
"union-china-pay",
"visa",
"mir",
"maestro"
"maestro",
"rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3596,7 +3597,8 @@
"Union China Pay",
"Visa",
"MIR",
"Maestro"
"Maestro",
"Rupay"
],
"in": "path"
},
@@ -4547,7 +4549,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
+132 -87
View File
@@ -3577,7 +3577,7 @@
"parameters": [
{
"name": "code",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3597,7 +3597,8 @@
"union-china-pay",
"visa",
"mir",
"maestro"
"maestro",
"rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3616,7 +3617,8 @@
"Union China Pay",
"Visa",
"MIR",
"Maestro"
"Maestro",
"Rupay"
],
"in": "path"
},
@@ -8012,7 +8014,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -11810,7 +11812,7 @@
},
"x-appwrite": {
"method": "getCertificate",
"weight": 134,
"weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11919,7 +11921,7 @@
},
"x-appwrite": {
"method": "getPubSub",
"weight": 130,
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11945,56 +11947,6 @@
]
}
},
"\/health\/queue": {
"get": {
"summary": "Get queue",
"operationId": "healthGetQueue",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"health"
],
"description": "Check the Appwrite queue messaging servers are up and connection is successful.",
"responses": {
"200": {
"description": "Health Status",
"schema": {
"$ref": "#\/definitions\/healthStatus"
}
}
},
"x-appwrite": {
"method": "getQueue",
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Key": []
}
]
}
},
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -12019,7 +11971,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
"weight": 136,
"weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12080,7 +12032,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
"weight": 135,
"weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12141,7 +12093,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
"weight": 137,
"weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12211,7 +12163,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
"weight": 138,
"weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12309,8 +12261,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
"v1-usage",
"v1-usage-dump",
"v1-stats-resources",
"v1-stats-usage",
"v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -12357,7 +12310,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
"weight": 142,
"weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12418,7 +12371,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
"weight": 133,
"weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12479,7 +12432,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
"weight": 139,
"weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12540,7 +12493,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
"weight": 140,
"weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12601,7 +12554,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
"weight": 141,
"weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12638,10 +12591,10 @@
]
}
},
"\/health\/queue\/usage": {
"\/health\/queue\/stats-resources": {
"get": {
"summary": "Get usage queue",
"operationId": "healthGetQueueUsage",
"summary": "Get stats resources queue",
"operationId": "healthGetQueueStatsResources",
"consumes": [
"application\/json"
],
@@ -12651,7 +12604,7 @@
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -12661,13 +12614,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"method": "getQueueStatsResources",
"weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
"demo": "health\/get-queue-stats-resources.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12699,10 +12652,71 @@
]
}
},
"\/health\/queue\/usage-dump": {
"\/health\/queue\/stats-usage": {
"get": {
"summary": "Get stats usage queue",
"operationId": "healthGetQueueUsage",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"responses": {
"200": {
"description": "Health Queue",
"schema": {
"$ref": "#\/definitions\/healthQueue"
}
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "threshold",
"description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
"required": false,
"type": "integer",
"format": "int32",
"default": 5000,
"in": "query"
}
]
}
},
"\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
"operationId": "healthGetQueueUsageDump",
"operationId": "healthGetQueueStatsUsageDump",
"consumes": [
"application\/json"
],
@@ -12722,13 +12736,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsageDump",
"method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
"demo": "health\/get-queue-stats-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12784,7 +12798,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
"weight": 132,
"weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12945,7 +12959,7 @@
},
"x-appwrite": {
"method": "getTime",
"weight": 131,
"weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -18070,19 +18084,19 @@
},
"endpoint": {
"type": "string",
"description": "Source's Appwrite Endpoint",
"description": "Source Appwrite endpoint",
"default": null,
"x-example": "https:\/\/example.com"
},
"projectId": {
"type": "string",
"description": "Source's Project ID",
"description": "Source Project ID",
"default": null,
"x-example": "<PROJECT_ID>"
},
"apiKey": {
"type": "string",
"description": "Source's API Key",
"description": "Source API Key",
"default": null,
"x-example": "<API_KEY>"
}
@@ -36608,6 +36622,21 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
},
"imageTransformations": {
"type": "array",
"description": "Aggregated number of files transformations per period.",
"items": {
"type": "object",
"$ref": "#\/definitions\/metric"
},
"x-example": []
},
"imageTransformationsTotal": {
"type": "integer",
"description": "Total aggregated number of files transformations.",
"x-example": 0,
"format": "int32"
}
},
"required": [
@@ -36615,7 +36644,9 @@
"filesTotal",
"filesStorageTotal",
"files",
"storage"
"storage",
"imageTransformations",
"imageTransformationsTotal"
]
},
"usageFunctions": {
@@ -37185,6 +37216,18 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
},
"imageTransformationsTotal": {
"type": "integer",
"description": "An array of aggregated number of image transformations.",
"x-example": 0,
"format": "int32"
},
"imageTransformations": {
"type": "integer",
"description": "Total aggregated number of image transformations.",
"x-example": 0,
"format": "int32"
}
},
"required": [
@@ -37216,7 +37259,9 @@
"authPhoneEstimate",
"authPhoneCountryBreakdown",
"databasesReads",
"databasesWrites"
"databasesWrites",
"imageTransformationsTotal",
"imageTransformations"
]
},
"headers": {
+97 -83
View File
@@ -3261,7 +3261,7 @@
"parameters": [
{
"name": "code",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
"description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3281,7 +3281,8 @@
"union-china-pay",
"visa",
"mir",
"maestro"
"maestro",
"rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3300,7 +3301,8 @@
"Union China Pay",
"Visa",
"MIR",
"Maestro"
"Maestro",
"Rupay"
],
"in": "path"
},
@@ -7552,7 +7554,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -10689,7 +10691,7 @@
},
"x-appwrite": {
"method": "getCertificate",
"weight": 134,
"weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10800,7 +10802,7 @@
},
"x-appwrite": {
"method": "getPubSub",
"weight": 130,
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10827,57 +10829,6 @@
]
}
},
"\/health\/queue": {
"get": {
"summary": "Get queue",
"operationId": "healthGetQueue",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"health"
],
"description": "Check the Appwrite queue messaging servers are up and connection is successful.",
"responses": {
"200": {
"description": "Health Status",
"schema": {
"$ref": "#\/definitions\/healthStatus"
}
}
},
"x-appwrite": {
"method": "getQueue",
"weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
]
}
},
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -10902,7 +10853,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
"weight": 136,
"weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10964,7 +10915,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
"weight": 135,
"weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11026,7 +10977,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
"weight": 137,
"weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11097,7 +11048,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
"weight": 138,
"weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11197,8 +11148,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
"v1-usage",
"v1-usage-dump",
"v1-stats-resources",
"v1-stats-usage",
"v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -11245,7 +11197,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
"weight": 142,
"weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11307,7 +11259,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
"weight": 133,
"weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11369,7 +11321,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
"weight": 139,
"weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11431,7 +11383,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
"weight": 140,
"weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11493,7 +11445,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
"weight": 141,
"weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11531,10 +11483,10 @@
]
}
},
"\/health\/queue\/usage": {
"\/health\/queue\/stats-resources": {
"get": {
"summary": "Get usage queue",
"operationId": "healthGetQueueUsage",
"summary": "Get stats resources queue",
"operationId": "healthGetQueueStatsResources",
"consumes": [
"application\/json"
],
@@ -11544,7 +11496,7 @@
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -11554,13 +11506,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"method": "getQueueStatsResources",
"weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
"demo": "health\/get-queue-stats-resources.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11593,10 +11545,72 @@
]
}
},
"\/health\/queue\/usage-dump": {
"\/health\/queue\/stats-usage": {
"get": {
"summary": "Get stats usage queue",
"operationId": "healthGetQueueUsage",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"health"
],
"description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
"responses": {
"200": {
"description": "Health Queue",
"schema": {
"$ref": "#\/definitions\/healthQueue"
}
}
},
"x-appwrite": {
"method": "getQueueUsage",
"weight": 143,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "health.read",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "threshold",
"description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
"required": false,
"type": "integer",
"format": "int32",
"default": 5000,
"in": "query"
}
]
}
},
"\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
"operationId": "healthGetQueueUsageDump",
"operationId": "healthGetQueueStatsUsageDump",
"consumes": [
"application\/json"
],
@@ -11616,13 +11630,13 @@
}
},
"x-appwrite": {
"method": "getQueueUsageDump",
"method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "health\/get-queue-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
"demo": "health\/get-queue-stats-usage-dump.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11679,7 +11693,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
"weight": 132,
"weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11843,7 +11857,7 @@
},
"x-appwrite": {
"method": "getTime",
"weight": 131,
"weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
+8 -6
View File
@@ -2400,7 +2400,7 @@ App::put('/v1/account/sessions/phone')
App::post('/v1/account/tokens/phone')
->alias('/v1/account/sessions/phone')
->desc('Create phone token')
->groups(['api', 'account'])
->groups(['api', 'account', 'auth'])
->label('scope', 'sessions.write')
->label('auth.type', 'phone')
->label('audits.event', 'session.create')
@@ -2715,13 +2715,15 @@ App::get('/v1/account/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new EventAudit($dbForProject);
$logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset);
$logs = $audit->getLogsByUser($user->getInternalId(), $queries);
$output = [];
@@ -2750,7 +2752,7 @@ App::get('/v1/account/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByUser($user->getInternalId()),
'total' => $audit->countLogsByUser($user->getInternalId(), $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+41 -26
View File
@@ -23,6 +23,7 @@ use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Conflict as ConflictException;
@@ -242,8 +243,8 @@ function updateAttribute(
string $filter = null,
string|bool|int|float $default = null,
bool $required = null,
int|float $min = null,
int|float $max = null,
int|float|null $min = null,
int|float|null $max = null,
array $elements = null,
array $options = [],
string $newKey = null,
@@ -299,6 +300,9 @@ function updateAttribute(
switch ($attribute->getAttribute('format')) {
case APP_DATABASE_ATTRIBUTE_INT_RANGE:
case APP_DATABASE_ATTRIBUTE_FLOAT_RANGE:
$min ??= $attribute->getAttribute('formatOptions')['min'];
$max ??= $attribute->getAttribute('formatOptions')['max'];
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
}
@@ -669,13 +673,15 @@ App::get('/v1/databases/:databaseId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -723,7 +729,7 @@ App::get('/v1/databases/:databaseId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -1057,14 +1063,21 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId . '/collection/' . $collectionId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -1112,7 +1125,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -1550,8 +1563,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
// Ensure attribute default is within range
$min = \is_null($min) ? PHP_INT_MIN : $min;
$max = \is_null($max) ? PHP_INT_MAX : $max;
$min ??= PHP_INT_MIN;
$max ??= PHP_INT_MAX;
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
@@ -1627,8 +1640,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
// Ensure attribute default is within range
$min = \is_null($min) ? -PHP_FLOAT_MAX : $min;
$max = \is_null($max) ? PHP_FLOAT_MAX : $max;
$min ??= -PHP_FLOAT_MAX;
$max ??= PHP_FLOAT_MAX;
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
@@ -2339,8 +2352,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Integer(), 'Minimum value to enforce on new documents')
->param('max', null, new Integer(), 'Maximum value to enforce on new documents')
->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true)
->param('max', null, new Integer(), 'Maximum value to enforce on new documents', true)
->param('default', null, new Nullable(new Integer()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
@@ -2398,8 +2411,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents')
->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents')
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true)
->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents', true)
->param('default', null, new Nullable(new FloatValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
@@ -3709,13 +3722,15 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId . '/collection/' . $collectionId . '/document/' . $document->getId();
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -3763,7 +3778,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+20 -38
View File
@@ -6,13 +6,14 @@ use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\Validator\Headers;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Tasks\ScheduleExecutions;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -194,9 +195,12 @@ App::post('/v1/functions')
->inject('user')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('queueForWebhooks')
->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('dbForPlatform')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Database $dbForPlatform, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
// Temporary abuse check
@@ -386,51 +390,29 @@ App::post('/v1/functions')
]))
);
/** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
$ruleCreate
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
/** Trigger Webhook */
$queueForWebhooks
->from($ruleCreate)
->trigger();
/** Trigger Functions */
$ruleCreate
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
$queueForFunctions
->from($ruleCreate)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].create', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
/** Trigger Realtime Events */
$queueForRealtime
->from($ruleCreate)
->setSubscribers(['console', $project->getId()])
->trigger();
}
$queueForEvents->setParam('functionId', $function->getId());
+28 -20
View File
@@ -994,13 +994,15 @@ App::get('/v1/messaging/providers/:providerId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'provider/' . $providerId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
foreach ($logs as $i => &$log) {
@@ -1047,7 +1049,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2222,13 +2224,15 @@ App::get('/v1/messaging/topics/:topicId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'topic/' . $topicId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -2276,7 +2280,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2632,13 +2636,15 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'subscriber/' . $subscriberId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -2686,7 +2692,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -3397,13 +3403,15 @@ App::get('/v1/messaging/messages/:messageId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'message/' . $messageId;
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -3451,7 +3459,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+16 -4
View File
@@ -40,14 +40,18 @@ App::get('/v1/project/usage')
->param('endDate', '', new DateTimeValidator(), 'End date for the usage')
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('getLogsDB')
->inject('smsRates')
->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject, array $smsRates) {
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
$lastDay = (new DateTime($endDate))->format($format);
$dbForLogs = call_user_func($getLogsDB, $project);
$metrics = [
'total' => [
METRIC_EXECUTIONS,
@@ -63,6 +67,7 @@ App::get('/v1/project/usage')
METRIC_BUILDS_STORAGE,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_WRITES,
METRIC_FILES_IMAGES_TRANSFORMED,
],
'period' => [
METRIC_NETWORK_REQUESTS,
@@ -75,6 +80,7 @@ App::get('/v1/project/usage')
METRIC_BUILDS_MB_SECONDS,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_WRITES,
METRIC_FILES_IMAGES_TRANSFORMED,
]
];
@@ -93,9 +99,11 @@ App::get('/v1/project/usage')
'1d' => 'Y-m-d\T00:00:00.000P',
};
Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
foreach ($metrics['total'] as $metric) {
$result = $dbForProject->findOne('stats', [
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
$result = $db->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
@@ -103,7 +111,9 @@ App::get('/v1/project/usage')
}
foreach ($metrics['period'] as $metric) {
$results = $dbForProject->find('stats', [
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
$results = $db->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::greaterThanEqual('time', $firstDay),
@@ -363,6 +373,8 @@ App::get('/v1/project/usage')
'authPhoneTotal' => $authPhoneTotal,
'authPhoneEstimate' => $authPhoneEstimate,
'authPhoneCountryBreakdown' => $authPhoneCountryBreakdown,
'imageTransformations' => $usage[METRIC_FILES_IMAGES_TRANSFORMED],
'imageTransformationsTotal' => $total[METRIC_FILES_IMAGES_TRANSFORMED],
]), Response::MODEL_USAGE_PROJECT);
});
+23 -22
View File
@@ -6,7 +6,6 @@ use Appwrite\Auth\Auth;
use Appwrite\ClamAV\Network;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\SDK\AuthType;
@@ -935,15 +934,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->param('rotation', 0, new Range(-360, 360), 'Preview image rotation in degrees. Pass an integer between -360 and 360.', true)
->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('mode')
->inject('deviceForFiles')
->inject('deviceForLocal')
->inject('queueForStatsUsage')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal, StatsUsage $queueForStatsUsage) {
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Response $response, Database $dbForProject, Device $deviceForFiles, Device $deviceForLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
@@ -1071,22 +1066,19 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
$queueForStatsUsage
->addMetric(METRIC_FILES_TRANSFORMATIONS, 1)
->addMetric(str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_TRANSFORMATIONS), 1)
;
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
//Do not update transformedAt if it's a console user
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
}
}
$response
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
->setContentType($contentType)
->file($data)
;
->file($data);
unset($image);
});
@@ -1879,9 +1871,12 @@ App::get('/v1/storage/:bucketId/usage')
->param('bucketId', '', new UID(), 'Bucket ID.')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) {
->inject('getLogsDB')
->action(function (string $bucketId, string $range, Response $response, Document $project, Database $dbForProject, callable $getLogsDB) {
$dbForLogs = call_user_func($getLogsDB, $project);
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
@@ -1894,12 +1889,16 @@ App::get('/v1/storage/:bucketId/usage')
$metrics = [
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES),
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE),
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED),
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
Authorization::skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
$db = ($metric === str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED))
? $dbForLogs
: $dbForProject;
$result = $db->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
@@ -1907,7 +1906,7 @@ App::get('/v1/storage/:bucketId/usage')
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats', [
$results = $db->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
@@ -1948,5 +1947,7 @@ App::get('/v1/storage/:bucketId/usage')
'filesStorageTotal' => $usage[$metrics[1]]['total'],
'files' => $usage[$metrics[0]]['data'],
'storage' => $usage[$metrics[1]]['data'],
'imageTransformations' => $usage[$metrics[2]]['data'],
'imageTransformationsTotal' => $usage[$metrics[2]]['total'],
]), Response::MODEL_USAGE_BUCKETS);
});
+10 -8
View File
@@ -488,7 +488,7 @@ App::post('/v1/teams/:teamId/memberships')
}
$email = \strtolower($email);
$name = (empty($name)) ? $email : $name;
$name = empty($name) ? $email : $name;
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -507,7 +507,7 @@ App::post('/v1/teams/:teamId/memberships')
}
$email = $invitee->getAttribute('email', '');
$phone = $invitee->getAttribute('phone', '');
$name = empty($name) ? $invitee->getAttribute('name', '') : $name;
$name = $invitee->getAttribute('name', '') ?: $name;
} elseif (!empty($email)) {
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
if (!$invitee->isEmpty() && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
@@ -715,7 +715,7 @@ App::post('/v1/teams/:teamId/memberships')
->setSubject($subject)
->setBody($body)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name'))
->setName($invitee->getAttribute('name', ''))
->setVariables($emailVariables)
->trigger();
@@ -1362,13 +1362,15 @@ App::get('/v1/teams/:teamId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$resource = 'team/' . $team->getId();
$logs = $audit->getLogsByResource($resource, $limit, $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -1415,7 +1417,7 @@ App::get('/v1/teams/:teamId/logs')
}
}
$response->dynamic(new Document([
'total' => $audit->countLogsByResource($resource),
'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+7 -5
View File
@@ -862,13 +862,15 @@ App::get('/v1/users/:userId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset);
$logs = $audit->getLogsByUser($user->getInternalId(), $queries);
$output = [];
@@ -915,7 +917,7 @@ App::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByUser($user->getInternalId()),
'total' => $audit->countLogsByUser($user->getInternalId(), $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+16 -2
View File
@@ -737,7 +737,8 @@ App::options()
->inject('geodb')
->inject('isResourceBlocked')
->inject('previewHostname')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
->inject('project')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project) {
/*
* Appwrite Router
*/
@@ -760,6 +761,16 @@ App::options()
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
->noContent();
/** OPTIONS requests in utopia do not execute shutdown handlers, as a result we need to track the OPTIONS requests explicitly
* @see https://github.com/utopia-php/http/blob/0.33.16/src/App.php#L825-L855
*/
$queueForStatsUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize())
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
->setProject($project)
->trigger();
});
App::error()
@@ -874,7 +885,10 @@ App::error()
}
}
if ($publish && $project->getId() !== 'console') {
/**
* If its not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
*/
if (!$publish && $project->getId() !== 'console') {
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
+8 -6
View File
@@ -534,7 +534,7 @@ App::init()
if ($type === 'bucket') {
$bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -560,11 +560,13 @@ App::init()
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
//Do not update transformedAt if it's a console user
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
}
}
}
+48
View File
@@ -304,6 +304,54 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
}
});
$projectCollections = $collections['projects'];
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$sharedTablesV2 = \array_diff($sharedTables, $sharedTablesV1);
$cache = $app->getResource('cache');
foreach ($sharedTablesV2 as $hostname) {
$adapter = $pools
->get($hostname)
->pop()
->getResource();
$dbForProject = (new Database($adapter, $cache))
->setDatabase('appwrite')
->setSharedTables(true)
->setTenant(null)
->setNamespace(System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''));
try {
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
} catch (Duplicate) {
Console::success('[Setup] - Skip: metadata table already exists');
}
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForProject);
$audit->setup();
}
foreach ($projectCollections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
if (!$dbForProject->getCollection($key)->isEmpty()) {
continue;
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
Console::success('[Setup] - Creating project collection: ' . $collection['$id'] . '...');
$dbForProject->createCollection($key, $attributes, $indexes);
}
}
$pools->reclaim();
Console::success('[Setup] - Server database init completed...');
});
+2 -3
View File
@@ -309,6 +309,8 @@ const METRIC_WEBHOOKS = 'webhooks';
const METRIC_PLATFORMS = 'platforms';
const METRIC_PROVIDERS = 'providers';
const METRIC_TOPICS = 'topics';
const METRIC_TARGETS = 'targets';
const METRIC_PROVIDER_TYPE_TARGETS = '{providerType}.targets';
const METRIC_KEYS = 'keys';
const METRIC_RESOURCE_TYPE_ID_BUILDS = '{resourceType}.{resourceInternalId}.builds';
const METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE = '{resourceType}.{resourceInternalId}.builds.storage';
@@ -1198,9 +1200,6 @@ App::setResource('queueForAudits', function (Queue\Publisher $publisher) {
App::setResource('queueForFunctions', function (Queue\Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
App::setResource('queueForUsage', function (Queue\Publisher $publisher) {
return new Usage($publisher);
}, ['publisher']);
App::setResource('queueForCertificates', function (Queue\Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
+10 -10
View File
@@ -13,12 +13,12 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\StatsUsageDump;
/** remove */
use Appwrite\Event\Usage;
use Appwrite\Event\UsageDump;
/** /remove */
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
@@ -269,14 +269,6 @@ Server::setResource('consumer', function (Group $pools) {
return $pools->get('consumer')->pop()->getResource();
}, ['pools']);
Server::setResource('queueForUsage', function (Publisher $publisher) {
return new Usage($publisher);
}, ['publisher']);
Server::setResource('queueForUsageDump', function (Publisher $publisher) {
return new UsageDump($publisher);
}, ['publisher']);
Server::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
@@ -313,10 +305,18 @@ Server::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
}, ['publisher']);
Server::setResource('queueForWebhooks', function (Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
Server::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
Server::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/worker.php usage $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/worker.php usage-dump $@
+10 -10
View File
@@ -45,21 +45,21 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.16.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.50.*",
"utopia-php/abuse": "0.51.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.51.*",
"utopia-php/audit": "0.54.0",
"utopia-php/cache": "0.11.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.59.0",
"utopia-php/database": "0.60.*",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/framework": "0.33.17",
"utopia-php/fetch": "0.3.*",
"utopia-php/image": "0.7.*",
"utopia-php/image": "0.8.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.14.*",
"utopia-php/messaging": "0.16.*",
"utopia-php/migration": "0.6.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.3",
@@ -85,11 +85,11 @@
"require-dev": {
"ext-fileinfo": "*",
"appwrite/sdk-generator": "0.40.*",
"phpunit/phpunit": "9.5.20",
"phpunit/phpunit": "9.*",
"swoole/ide-helper": "5.1.2",
"textalk/websocket": "1.5.7",
"laravel/pint": "^1.14",
"phpbench/phpbench": "^1.2"
"textalk/websocket": "1.5.*",
"laravel/pint": "1.*",
"phpbench/phpbench": "1.*"
},
"provide": {
"ext-phpiredis": "*"
Generated
+188 -535
View File
File diff suppressed because it is too large Load Diff
+46 -11
View File
@@ -7,10 +7,17 @@ use Utopia\Database\Document;
class Realtime extends Event
{
protected array $subscribers = [];
public function __construct()
{
}
/**
* Get Realtime payload for this event.
*
* @return array
*/
public function getRealtimePayload(): array
{
$payload = [];
@@ -24,6 +31,28 @@ class Realtime extends Event
return $payload;
}
/**
* Set subscribers for this realtime event.
*
* @param array $subscribers
* @return array
*/
public function setSubscribers(array $subscribers): self
{
$this->subscribers = $subscribers;
return $this;
}
/**
* Get subscribers for this realtime event.
*
* @return array
*/
public function getSubscribers(): array
{
return $this->subscribers;
}
/**
* Execute Event.
*
@@ -53,17 +82,23 @@ class Realtime extends Event
bucket: $bucket,
);
RealtimeAdapter::send(
projectId: $target['projectId'] ?? $this->getProject()->getId(),
payload: $this->getRealtimePayload(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $this->getParam('userId')
]
);
$projectIds = !empty($this->getSubscribers())
? $this->getSubscribers()
: [$target['projectId'] ?? $this->getProject()->getId()];
foreach ($projectIds as $projectId) {
RealtimeAdapter::send(
projectId: $projectId,
payload: $this->getRealtimePayload(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $this->getParam('userId')
]
);
}
return true;
}
-78
View File
@@ -1,78 +0,0 @@
<?php
namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
class Usage extends Event
{
protected array $metrics = [];
protected array $reduce = [];
public function __construct(protected Publisher $publisher)
{
parent::__construct($publisher);
$this
->setQueue(Event::USAGE_QUEUE_NAME)
->setClass(Event::USAGE_CLASS_NAME);
}
/**
* Add reduce.
*
* @param Document $document
* @return self
*/
public function addReduce(Document $document): self
{
$this->reduce[] = $document;
return $this;
}
/**
* Add metric.
*
* @param string $key
* @param int $value
* @return self
*/
public function addMetric(string $key, int $value): self
{
$this->metrics[] = [
'key' => $key,
'value' => $value,
];
return $this;
}
/**
* Prepare the payload for the usage event.
*
* @return array
*/
protected function preparePayload(): array
{
return [
'project' => $this->project,
'reduce' => $this->reduce,
'metrics' => $this->metrics,
];
}
/**
* Sends metrics to the usage worker.
*
* @return string|bool
*/
public function trigger(): string|bool
{
parent::trigger();
$this->metrics = [];
return true;
}
}
-44
View File
@@ -1,44 +0,0 @@
<?php
namespace Appwrite\Event;
use Utopia\Queue\Publisher;
class UsageDump extends Event
{
protected array $stats;
public function __construct(protected Publisher $publisher)
{
parent::__construct($publisher);
$this
->setQueue(Event::USAGE_DUMP_QUEUE_NAME)
->setClass(Event::USAGE_DUMP_CLASS_NAME);
}
/**
* Add Stats.
*
* @param array $stats
* @return self
*/
public function setStats(array $stats): self
{
$this->stats = $stats;
return $this;
}
/**
* Prepare the payload for the usage dump event.
*
* @return array
*/
protected function preparePayload(): array
{
return [
'stats' => $this->stats,
];
}
}
@@ -14,10 +14,6 @@ use Appwrite\Platform\Workers\Migrations;
use Appwrite\Platform\Workers\StatsResources;
use Appwrite\Platform\Workers\StatsUsage;
use Appwrite\Platform\Workers\StatsUsageDump;
/** remove */
use Appwrite\Platform\Workers\Usage;
use Appwrite\Platform\Workers\UsageDump;
/** /remove */
use Appwrite\Platform\Workers\Webhooks;
use Utopia\Platform\Service;
@@ -40,10 +36,6 @@ class Workers extends Service
->addAction(StatsUsage::getName(), new StatsUsage())
->addAction(Migrations::getName(), new Migrations())
->addAction(StatsResources::getName(), new StatsResources())
/** Remove */
->addAction(UsageDump::getName(), new UsageDump())
->addAction(Usage::getName(), new Usage())
/** /remove */
;
}
}
+37 -20
View File
@@ -7,7 +7,6 @@ use Exception;
use Throwable;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization;
@@ -18,15 +17,16 @@ use Utopia\System\System;
class Audits extends Action
{
private const BATCH_SIZE_DEVELOPMENT = 1; // smaller batch size for development
private const BATCH_SIZE_PRODUCTION = 5_000;
private const BATCH_AGGREGATION_INTERVAL = 60; // in seconds
protected const BATCH_SIZE_DEVELOPMENT = 1; // smaller batch size for development
protected const BATCH_SIZE_PRODUCTION = 5_000;
protected const BATCH_AGGREGATION_INTERVAL = 60; // in seconds
private int $lastTriggeredTime = 0;
private array $logs = [];
private function getBatchSize(): int
protected function getBatchSize(): int
{
return System::getEnv('_APP_ENV', 'development') === 'development'
? self::BATCH_SIZE_DEVELOPMENT
@@ -46,8 +46,9 @@ class Audits extends Action
$this
->desc('Audits worker')
->inject('message')
->inject('dbForProject')
->callback(fn ($message, $dbForProject) => $this->action($message, $dbForProject));
->inject('getProjectDB')
->inject('project')
->callback([$this, 'action']);
$this->lastTriggeredTime = time();
}
@@ -55,14 +56,15 @@ class Audits extends Action
/**
* @param Message $message
* @param Database $dbForProject
* @param callable $getProjectDB
* @param Document $project
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
* @throws Authorization
* @throws Structure
*/
public function action(Message $message, Database $dbForProject): void
public function action(Message $message, callable $getProjectDB, Document $project): void
{
$payload = $message->getPayload() ?? [];
@@ -103,27 +105,42 @@ class Audits extends Action
'timestamp' => DateTime::formatTz(DateTime::now())
];
$this->logs[] = $eventData;
if (isset($this->logs[$project->getInternalId()])) {
$this->logs[$project->getInternalId()]['logs'][] = $eventData;
} else {
$this->logs[$project->getInternalId()] = [
'project' => new Document([
'$id' => $project->getId(),
'$internalId' => $project->getInternalId(),
'database' => $project->getAttribute('database'),
]),
'logs' => [$eventData]
];
}
// Check if we should process the batch by checking both for the batch size and the elapsed time
$batchSize = $this->getBatchSize();
$shouldProcessBatch = count($this->logs) >= $batchSize;
if (!$shouldProcessBatch && count($this->logs) > 0) {
$shouldProcessBatch = (time() - $this->lastTriggeredTime) >= self::BATCH_AGGREGATION_INTERVAL;
$shouldProcessBatch = \count($this->logs) >= $batchSize;
if (!$shouldProcessBatch && \count($this->logs) > 0) {
$shouldProcessBatch = (\time() - $this->lastTriggeredTime) >= self::BATCH_AGGREGATION_INTERVAL;
}
if ($shouldProcessBatch) {
Console::log('Processing batch with ' . count($this->logs) . ' events');
$audit = new Audit($dbForProject);
try {
$audit->logBatch($this->logs);
Console::success('Audit logs processed successfully');
foreach ($this->logs as $internalId => $projectLogs) {
$dbForProject = $getProjectDB($projectLogs['project']);
Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events');
$audit = new Audit($dbForProject);
$audit->logBatch($projectLogs['logs']);
Console::success('Audit logs processed successfully');
unset($this->logs[$internalId]);
}
} catch (Throwable $e) {
Console::error('Error processing audit logs: ' . $e->getMessage());
} finally {
// Clear the pending events after successful batch processing
$this->logs = [];
$this->lastTriggeredTime = time();
}
}
+56 -73
View File
@@ -5,8 +5,9 @@ namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Vcs\Comment;
use Exception;
@@ -49,15 +50,17 @@ class Builds extends Action
->inject('project')
->inject('dbForPlatform')
->inject('queueForEvents')
->inject('queueForWebhooks')
->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('queueForStatsUsage')
->inject('cache')
->inject('dbForProject')
->inject('deviceForFunctions')
->inject('isResourceBlocked')
->inject('log')
->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) =>
$this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log));
->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) =>
$this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log));
}
/**
@@ -65,7 +68,9 @@ class Builds extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Event $queueForEvents
* @param Webhook $queueForWebhooks
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param StatsUsage $queueForStatsUsage
* @param Cache $cache
* @param Database $dbForProject
@@ -74,7 +79,7 @@ class Builds extends Action
* @return void
* @throws \Utopia\Database\Exception
*/
public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void
public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void
{
$payload = $message->getPayload() ?? [];
@@ -95,7 +100,7 @@ class Builds extends Action
case BUILD_TYPE_RETRY:
Console::info('Creating build for deployment: ' . $deployment->getId());
$github = new GitHub($cache);
$this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log);
$this->buildDeployment($deviceForFunctions, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log);
break;
default:
@@ -105,7 +110,9 @@ class Builds extends Action
/**
* @param Device $deviceForFunctions
* @param Webhook $queueForWebhooks
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param Event $queueForEvents
* @param StatsUsage $queueForStatsUsage
* @param Database $dbForPlatform
@@ -120,7 +127,7 @@ class Builds extends Action
* @throws \Utopia\Database\Exception
* @throws Exception
*/
protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void
protected function buildDeployment(Device $deviceForFunctions, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void
{
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
@@ -158,10 +165,7 @@ class Builds extends Action
}
// Realtime preparation
$allEvents = Event::generateEvents('functions.[functionId].deployments.[deploymentId].update', [
'functionId' => $function->getId(),
'deploymentId' => $deployment->getId()
]);
$event = "functions.[functionId].deployments.[deploymentId].update";
$startTime = DateTime::now();
$durationStart = \microtime(true);
@@ -375,21 +379,16 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
/**
* Send realtime Event
* Trigger Realtime Event
*/
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $build,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
$queueForRealtime
->setProject($project)
->setSubscribers(['console'])
->setEvent($event)
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId())
->setPayload($build->getArrayCopy())
->trigger();
}
$tmpPath = '/tmp/builds/' . $buildId;
@@ -436,40 +435,34 @@ class Builds extends Action
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform);
}
/** Trigger Webhook */
$deploymentModel = new Deployment();
$deploymentUpdate =
$queueForEvents
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project)
->setEvent('functions.[functionId].deployments.[deploymentId].update')
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId())
->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules())));
$deploymentUpdate->trigger();
/** Trigger Webhook */
$queueForWebhooks
->from($deploymentUpdate)
->trigger();
/** Trigger Functions */
$queueForFunctions
->from($deploymentUpdate)
->trigger();
/** Trigger Realtime */
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $build,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
/** Trigger Realtime Event */
$queueForRealtime
->setProject($project)
->setSubscribers(['console'])
->setEvent($event)
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId())
->setPayload($build->getArrayCopy())
->trigger();
$vars = [];
@@ -562,12 +555,12 @@ class Builds extends Action
$err = $error;
}
}),
Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, &$err, &$isCanceled) {
Co\go(function () use ($executor, $project, $function, $deployment, &$response, &$build, $dbForProject, $event, &$err, $queueForRealtime, &$isCanceled) {
try {
$executor->getLogs(
deploymentId: $deployment->getId(),
projectId: $project->getId(),
callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $allEvents, $project, &$isCanceled) {
callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $event, $project, $function, $deployment, $queueForRealtime, &$isCanceled) {
if ($isCanceled) {
return;
}
@@ -592,21 +585,16 @@ class Builds extends Action
$build = $dbForProject->updateDocument('builds', $build->getId(), $build);
/**
* Send realtime Event
* Trigger Realtime Event
*/
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $build,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
$queueForRealtime
->setProject($project)
->setSubscribers(['console'])
->setEvent($event)
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId())
->setPayload($build->getArrayCopy())
->trigger();
}
}
);
@@ -694,21 +682,16 @@ class Builds extends Action
}
} finally {
/**
* Send realtime Event
* Trigger Realtime Event
*/
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $build,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
$queueForRealtime
->setProject($project)
->setSubscribers(['console'])
->setEvent($event)
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId())
->setPayload($build->getArrayCopy())
->trigger();
/** Trigger usage queue */
if ($build->getAttribute('status') === 'ready') {
+28 -43
View File
@@ -6,7 +6,8 @@ use Appwrite\Certificates\Adapter as CertificatesAdapter;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model\Rule;
@@ -46,12 +47,14 @@ class Certificates extends Action
->inject('dbForPlatform')
->inject('queueForMails')
->inject('queueForEvents')
->inject('queueForWebhooks')
->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('log')
->inject('certificates')
->callback(
fn (Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates) =>
$this->action($message, $dbForPlatform, $queueForMails, $queueForEvents, $queueForFunctions, $log, $certificates)
fn (Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates) =>
$this->action($message, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates)
);
}
@@ -60,14 +63,16 @@ class Certificates extends Action
* @param Database $dbForPlatform
* @param Mail $queueForMails
* @param Event $queueForEvents
* @param Webhook $queueForWebhooks
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param Log $log
* @param CertificatesAdapter $certificates
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
*/
public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates): void
public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates): void
{
$payload = $message->getPayload() ?? [];
@@ -81,7 +86,7 @@ class Certificates extends Action
$log->addTag('domain', $domain->get());
$this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForFunctions, $log, $certificates, $skipRenewCheck);
$this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck);
}
/**
@@ -90,13 +95,14 @@ class Certificates extends Action
* @param Mail $queueForMails
* @param Event $queueForEvents
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param CertificatesAdapter $certificates
* @param bool $skipRenewCheck
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
*/
private function execute(Domain $domain, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates, bool $skipRenewCheck = false): void
private function execute(Domain $domain, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates, bool $skipRenewCheck = false): void
{
/**
* 1. Read arguments and validate domain
@@ -186,7 +192,7 @@ class Certificates extends Action
$certificate->setAttribute('updated', DateTime::now());
// Save all changes we made to certificate document into database
$this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForFunctions);
$this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
}
}
@@ -199,13 +205,14 @@ class Certificates extends Action
* @param Database $dbForPlatform Database connection for console
* @param Event $queueForEvents
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @return void
* @throws \Utopia\Database\Exception
* @throws Authorization
* @throws Conflict
* @throws Structure
*/
private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void
private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime): void
{
// Check if update or insert required
$certificateDocument = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain])]);
@@ -219,7 +226,7 @@ class Certificates extends Action
}
$certificateId = $certificate->getId();
$this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForFunctions);
$this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
}
/**
@@ -338,7 +345,7 @@ class Certificates extends Action
*
* @return void
*/
private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void
private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime): void
{
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
@@ -367,50 +374,28 @@ class Certificates extends Action
return;
}
/** Trigger Webhook */
$ruleModel = new Rule();
$queueForEvents
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project)
->setEvent('rules.[ruleId].update')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger();
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
/** Trigger Webhook */
$queueForWebhooks
->from($queueForEvents)
->trigger();
/** Trigger Functions */
$queueForFunctions
->setProject($project)
->setEvent('rules.[ruleId].update')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->from($queueForEvents)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].update', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
/** Trigger Realtime Events */
$queueForRealtime
->from($queueForEvents)
->setSubscribers(['console', $projectId])
->trigger();
}
}
}
+65 -71
View File
@@ -2,8 +2,7 @@
namespace Appwrite\Platform\Workers;
use Appwrite\Event\Event;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Event\Realtime;
use Exception;
use Utopia\CLI\Console;
use Utopia\Database\Database;
@@ -37,8 +36,9 @@ class Databases extends Action
->inject('project')
->inject('dbForPlatform')
->inject('dbForProject')
->inject('queueForRealtime')
->inject('log')
->callback(fn (Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Log $log) => $this->action($message, $project, $dbForPlatform, $dbForProject, $log));
->callback(fn (Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime, Log $log) => $this->action($message, $project, $dbForPlatform, $dbForProject, $queueForRealtime, $log));
}
/**
@@ -46,16 +46,17 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
* @param Realtime $queueForRealtime
* @param Log $log
* @return void
* @throws \Exception
*/
public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Log $log): void
public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime, Log $log): void
{
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
throw new \Exception('Missing payload');
throw new Exception('Missing payload');
}
$type = $payload['type'];
@@ -75,11 +76,11 @@ class Databases extends Action
match (\strval($type)) {
DATABASE_TYPE_DELETE_DATABASE => $this->deleteDatabase($database, $project, $dbForProject),
DATABASE_TYPE_DELETE_COLLECTION => $this->deleteCollection($database, $collection, $project, $dbForProject),
DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject),
DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject),
DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject),
DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject),
default => throw new \Exception('No database operation for type: ' . \strval($type)),
DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
default => throw new Exception('No database operation for type: ' . \strval($type)),
};
}
@@ -90,13 +91,14 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
* @throws \Exception
* @throws \Throwable
*/
private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void
private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -106,12 +108,7 @@ class Databases extends Action
}
$projectId = $project->getId();
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].update', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'attributeId' => $attribute->getId()
]);
$event = "databases.[databaseId].collections.[collectionId].attributes.[attributeId].update";
/**
* TODO @christyjacob4 verify if this is still the case
* Fetch attribute from the database, since with Resque float values are loosing informations.
@@ -169,7 +166,7 @@ class Databases extends Action
break;
default:
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new \Exception('Failed to create Attribute');
throw new Exception('Failed to create Attribute');
}
}
@@ -200,7 +197,7 @@ class Databases extends Action
throw $e;
} finally {
$this->trigger($database, $collection, $attribute, $project, $projectId, $events);
$this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
if (! $relatedCollection->isEmpty()) {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
@@ -217,13 +214,14 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
* @throws \Exception
* @throws \Throwable
**/
private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void
private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -233,15 +231,9 @@ class Databases extends Action
}
$projectId = $project->getId();
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'attributeId' => $attribute->getId()
]);
$event = 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete';
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
$status = $attribute->getAttribute('status', '');
$type = $attribute->getAttribute('type', '');
$project = $dbForPlatform->getDocument('projects', $projectId);
$options = $attribute->getAttribute('options', []);
@@ -312,7 +304,7 @@ class Databases extends Action
throw $e;
} finally {
$this->trigger($database, $collection, $attribute, $project, $projectId, $events);
$this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
}
// The underlying database removes/rebuilds indexes when attribute is removed
@@ -358,7 +350,7 @@ class Databases extends Action
}
if ($exists) { // Delete the duplicate if created, else update in db
$this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject);
$this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject, $queueForRealtime);
} else {
$dbForProject->updateDocument('indexes', $index->getId(), $index);
}
@@ -381,6 +373,7 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
@@ -388,7 +381,7 @@ class Databases extends Action
* @throws DatabaseException
* @throws \Throwable
*/
private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void
private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -398,12 +391,7 @@ class Databases extends Action
}
$projectId = $project->getId();
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].update', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'indexId' => $index->getId()
]);
$event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update';
$collectionId = $collection->getId();
$key = $index->getAttribute('key', '');
$type = $index->getAttribute('type', '');
@@ -430,7 +418,7 @@ class Databases extends Action
throw $e;
} finally {
$this->trigger($database, $collection, $index, $project, $projectId, $events);
$this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId);
}
}
@@ -442,6 +430,7 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
@@ -449,7 +438,7 @@ class Databases extends Action
* @throws DatabaseException
* @throws \Throwable
*/
private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void
private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -459,12 +448,7 @@ class Databases extends Action
}
$projectId = $project->getId();
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].delete', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'indexId' => $index->getId()
]);
$event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].delete';
$key = $index->getAttribute('key');
$status = $index->getAttribute('status', '');
$project = $dbForPlatform->getDocument('projects', $projectId);
@@ -490,7 +474,7 @@ class Databases extends Action
throw $e;
} finally {
$this->trigger($database, $collection, $index, $project, $projectId, $events);
$this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId());
}
}
@@ -532,7 +516,6 @@ class Databases extends Action
$collectionId = $collection->getId();
$collectionInternalId = $collection->getInternalId();
$databaseId = $database->getId();
$databaseInternalId = $database->getInternalId();
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
@@ -568,21 +551,21 @@ class Databases extends Action
/**
* @param string $collection collectionID
* @param string $collectionId
* @param array $queries
* @param Database $database
* @param callable|null $callback
* @return void
* @throws Exception
*/
protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
protected function deleteByGroup(string $collectionId, array $queries, Database $database, callable $callback = null): void
{
$start = \microtime(true);
try {
$documents = $database->deleteDocuments($collection, $queries);
$documents = $database->deleteDocuments($collectionId, $queries);
} catch (\Throwable $th) {
Console::error('Failed to delete documents for collection ' . $collection . ': ' . $th->getMessage());
Console::error('Failed to delete documents for collection ' . $collectionId . ': ' . $th->getMessage());
return;
}
@@ -598,31 +581,42 @@ class Databases extends Action
Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds");
}
/**
* @param Document $database
* @param Document $collection
* @param Document $project
* @param Realtime $queueForRealtime
* @param Document|null $attribute
* @param Document|null $index
* @return void
*/
protected function trigger(
Document $database,
Document $collection,
Document $attribute,
Document $project,
string $projectId,
array $events
string $event,
Realtime $queueForRealtime,
Document|null $attribute = null,
Document|null $index = null,
): void {
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $events[0],
payload: $attribute,
project: $project,
);
Realtime::send(
projectId: 'console',
payload: $attribute->getArrayCopy(),
events: $events,
channels: $target['channels'],
roles: $target['roles'],
options: [
'projectId' => $projectId,
'databaseId' => $database->getId(),
'collectionId' => $collection->getId()
]
);
$queueForRealtime
->setProject($project)
->setSubscribers(['console'])
->setEvent($event)
->setParam('databaseId', $database->getId())
->setParam('collectionId', $collection->getId());
if ($attribute !== null && !empty($attribute)) {
$queueForRealtime
->setParam('attributeId', $attribute->getId())
->setPayload($attribute->getArrayCopy());
}
if ($index !== null && !empty($index)) {
$queueForRealtime
->setParam('indexId', $index->getId())
->setPayload($index->getArrayCopy());
}
$queueForRealtime->trigger();
}
}
+25 -33
View File
@@ -5,8 +5,9 @@ namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Utopia\Response\Model\Execution;
use Exception;
use Executor\Executor;
@@ -44,15 +45,17 @@ class Functions extends Action
->inject('project')
->inject('message')
->inject('dbForProject')
->inject('queueForWebhooks')
->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('log')
->inject('isResourceBlocked')
->callback(fn (Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $log, $isResourceBlocked));
->callback(fn (Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $log, $isResourceBlocked));
}
public function action(Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked): void
public function action(Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked): void
{
$payload = $message->getPayload() ?? [];
@@ -136,7 +139,9 @@ class Functions extends Action
$this->execute(
log: $log,
dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents,
project: $project,
@@ -176,7 +181,9 @@ class Functions extends Action
$this->execute(
log: $log,
dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents,
project: $project,
@@ -198,7 +205,9 @@ class Functions extends Action
$this->execute(
log: $log,
dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents,
project: $project,
@@ -284,6 +293,7 @@ class Functions extends Action
* @param Log $log
* @param Database $dbForProject
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param StatsUsage $queueForStatsUsage
* @param Event $queueForEvents
* @param Document $project
@@ -307,7 +317,9 @@ class Functions extends Action
private function execute(
Log $log,
Database $dbForProject,
Webhook $queueForWebhooks,
Func $queueForFunctions,
Realtime $queueForRealtime,
StatsUsage $queueForStatsUsage,
Event $queueForEvents,
Document $project,
@@ -564,20 +576,20 @@ class Functions extends Action
;
}
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
/** Trigger Webhook */
$executionModel = new Execution();
$queueForEvents
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project)
->setUser($user)
->setEvent('functions.[functionId].executions.[executionId].update')
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->setPayload($execution->getArrayCopy(array_keys($executionModel->getRules())))
->setPayload($execution->getArrayCopy(array_keys($executionModel->getRules())));
/** Trigger Webhook */
$queueForWebhooks
->from($queueForEvents)
->trigger();
/** Trigger Functions */
@@ -585,31 +597,11 @@ class Functions extends Action
->from($queueForEvents)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('functions.[functionId].executions.[executionId].update', [
'functionId' => $function->getId(),
'executionId' => $execution->getId()
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $execution,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $execution->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $execution->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
/** Trigger Realtime Events */
$queueForRealtime
->from($queueForEvents)
->setSubscribers(['console', $project->getId()])
->trigger();
if (!empty($error)) {
throw new Exception($error, $errorCode);
+21 -39
View File
@@ -3,8 +3,7 @@
namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT;
use Appwrite\Event\Event;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Event\Realtime;
use Exception;
use Utopia\CLI\Console;
use Utopia\Config\Config;
@@ -54,13 +53,14 @@ class Migrations extends Action
->inject('dbForProject')
->inject('dbForPlatform')
->inject('logError')
->callback(fn (Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError) => $this->action($message, $project, $dbForProject, $dbForPlatform, $logError));
->inject('queueForRealtime')
->callback(fn (Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime) => $this->action($message, $project, $dbForProject, $dbForPlatform, $logError, $queueForRealtime));
}
/**
* @throws Exception
*/
public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError): void
public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime): void
{
$payload = $message->getPayload() ?? [];
@@ -87,7 +87,7 @@ class Migrations extends Action
return;
}
$this->processMigration($migration);
$this->processMigration($migration, $queueForRealtime);
}
/**
@@ -155,34 +155,16 @@ class Migrations extends Action
* @throws \Utopia\Database\Exception
* @throws Exception
*/
protected function updateMigrationDocument(Document $migration, Document $project): Document
protected function updateMigrationDocument(Document $migration, Document $project, Realtime $queueForRealtime): Document
{
/** Trigger Realtime */
$allEvents = Event::generateEvents('migrations.[migrationId].update', [
'migrationId' => $migration->getId(),
]);
$target = Realtime::fromPayload(
event: $allEvents[0],
payload: $migration,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $migration->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
);
Realtime::send(
projectId: $project->getId(),
payload: $migration->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
);
/** Trigger Realtime Events */
$queueForRealtime
->setProject($project)
->setSubscribers(['console', $project->getId()])
->setEvent('migrations.[migrationId].update')
->setParam('migrationId', $migration->getId())
->setPayload($migration->getArrayCopy())
->trigger();
return $this->dbForProject->updateDocument('migrations', $migration->getId(), $migration);
}
@@ -231,7 +213,7 @@ class Migrations extends Action
* @throws \Utopia\Database\Exception
* @throws Exception
*/
protected function processMigration(Document $migration): void
protected function processMigration(Document $migration, Realtime $queueForRealtime): void
{
$project = $this->project;
$projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId());
@@ -255,7 +237,7 @@ class Migrations extends Action
$migration->setAttribute('stage', 'processing');
$migration->setAttribute('status', 'processing');
$this->updateMigrationDocument($migration, $projectDocument);
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
$source = $this->processSource($migration);
$destination = $this->processDestination($migration, $tempAPIKey);
@@ -269,14 +251,14 @@ class Migrations extends Action
/** Start Transfer */
$migration->setAttribute('stage', 'migrating');
$this->updateMigrationDocument($migration, $projectDocument);
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
$transfer->run(
$migration->getAttribute('resources'),
function () use ($migration, $transfer, $projectDocument) {
function () use ($migration, $transfer, $projectDocument, $queueForRealtime) {
$migration->setAttribute('resourceData', json_encode($transfer->getCache()));
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
$this->updateMigrationDocument($migration, $projectDocument);
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
},
$migration->getAttribute('resourceId'),
$migration->getAttribute('resourceType')
@@ -313,7 +295,7 @@ class Migrations extends Action
}
$migration->setAttribute('errors', $errorMessages);
$this->updateMigrationDocument($migration, $projectDocument);
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
return;
}
@@ -354,7 +336,7 @@ class Migrations extends Action
$migration->setAttribute('errors', $errorMessages);
}
} finally {
$this->updateMigrationDocument($migration, $projectDocument);
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
if ($migration->getAttribute('status', '') === 'failed') {
Console::error('Migration('.$migration->getInternalId().':'.$migration->getId().') failed, Project('.$this->project->getInternalId().':'.$this->project->getId().')');
@@ -129,9 +129,20 @@ class StatsResources extends Action
]);
$teams = $dbForProject->count('teams');
$functions = $dbForProject->count('functions');
$messages = $dbForProject->count('messages');
$providers = $dbForProject->count('providers');
$topics = $dbForProject->count('topics');
$targets = $dbForProject->count('targets');
$emailTargets = $dbForProject->count('targets', [
Query::equal('providerType', [MESSAGE_TYPE_EMAIL])
]);
$pushTargets = $dbForProject->count('targets', [
Query::equal('providerType', [MESSAGE_TYPE_PUSH])
]);
$smsTargets = $dbForProject->count('targets', [
Query::equal('providerType', [MESSAGE_TYPE_SMS])
]);
$metrics = [
METRIC_DATABASES => $databases,
@@ -148,6 +159,10 @@ class StatsResources extends Action
METRIC_PROVIDERS => $providers,
METRIC_TOPICS => $topics,
METRIC_KEYS => $keys,
METRIC_TARGETS => $targets,
str_replace('{providerType}', MESSAGE_TYPE_EMAIL, METRIC_PROVIDER_TYPE_TARGETS) => $emailTargets,
str_replace('{providerType}', MESSAGE_TYPE_PUSH, METRIC_PROVIDER_TYPE_TARGETS) => $pushTargets,
str_replace('{providerType}', MESSAGE_TYPE_SMS, METRIC_PROVIDER_TYPE_TARGETS) => $smsTargets,
];
foreach ($metrics as $metric => $value) {
@@ -167,7 +182,7 @@ class StatsResources extends Action
}
try {
$this->countForDatabase($dbForProject, $dbForLogs, $region);
$this->countForDatabase($dbForProject, $region);
} catch (Throwable $th) {
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_database_{$project->getId()}"]);
}
@@ -227,42 +242,54 @@ class StatsResources extends Action
$this->createStatsDocuments($region, METRIC_FILES_IMAGES_TRANSFORMED, $totalImageTransformations);
}
protected function countForDatabase(Database $dbForProject, Database $dbForLogs, string $region)
protected function countForDatabase(Database $dbForProject, string $region)
{
$totalCollections = 0;
$totalDocuments = 0;
$this->foreachDocument($dbForProject, 'databases', [], function ($database) use ($dbForProject, $dbForLogs, $region, &$totalCollections, &$totalDocuments) {
$totalDatabaseStorage = 0;
$this->foreachDocument($dbForProject, 'databases', [], function ($database) use ($dbForProject, $region, &$totalCollections, &$totalDocuments, &$totalDatabaseStorage) {
$collections = $dbForProject->count('database_' . $database->getInternalId());
$metric = str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_COLLECTIONS);
$this->createStatsDocuments($region, $metric, $collections);
$documents = $this->countForCollections($dbForProject, $dbForLogs, $database, $region);
[$documents, $storage] = $this->countForCollections($dbForProject, $database, $region);
$totalDatabaseStorage += $storage;
$totalDocuments += $documents;
$totalCollections += $collections;
});
$this->createStatsDocuments($region, METRIC_COLLECTIONS, $totalCollections);
$this->createStatsDocuments($region, METRIC_DOCUMENTS, $totalDocuments);
$this->createStatsDocuments($region, METRIC_DATABASES_STORAGE, $totalDatabaseStorage);
}
protected function countForCollections(Database $dbForProject, Database $dbForLogs, Document $database, string $region): int
protected function countForCollections(Database $dbForProject, Document $database, string $region): array
{
$databaseDocuments = 0;
$this->foreachDocument($dbForProject, 'database_' . $database->getInternalId(), [], function ($collection) use ($dbForProject, $dbForLogs, $database, $region, &$totalCollections, &$databaseDocuments) {
$databaseStorage = 0;
$this->foreachDocument($dbForProject, 'database_' . $database->getInternalId(), [], function ($collection) use ($dbForProject, $database, $region, &$databaseStorage, &$databaseDocuments) {
$documents = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
$metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS);
$this->createStatsDocuments($region, $metric, $documents);
$databaseDocuments += $documents;
$collectionStorage = $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
$metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE);
$this->createStatsDocuments($region, $metric, $collectionStorage);
$databaseStorage += $collectionStorage;
});
$metric = str_replace(['{databaseInternalId}'], [$database->getInternalId()], METRIC_DATABASE_ID_DOCUMENTS);
$this->createStatsDocuments($region, $metric, $databaseDocuments);
return $databaseDocuments;
$metric = str_replace(['{databaseInternalId}'], [$database->getInternalId()], METRIC_DATABASE_ID_STORAGE);
$this->createStatsDocuments($region, $metric, $databaseStorage);
return [$databaseDocuments, $databaseStorage];
}
protected function countForFunctions(Database $dbForProject, Database $dbForLogs, string $region)
+9 -2
View File
@@ -17,13 +17,20 @@ class StatsUsage extends Action
private int $lastTriggeredTime = 0;
private int $keys = 0;
private const INFINITY_PERIOD = '_inf_';
private const KEYS_THRESHOLD = 10000;
private const BATCH_SIZE_DEVELOPMENT = 1;
private const BATCH_SIZE_PRODUCTION = 10_000;
public static function getName(): string
{
return 'stats-usage';
}
private function getBatchSize(): int
{
return System::getEnv('_APP_ENV', 'development') === 'development'
? self::BATCH_SIZE_DEVELOPMENT
: self::BATCH_SIZE_PRODUCTION;
}
/**
* @throws Exception
*/
@@ -86,7 +93,7 @@ class StatsUsage extends Action
// If keys crossed threshold or X time passed since the last send and there are some keys in the array ($this->stats)
if (
$this->keys >= self::KEYS_THRESHOLD ||
$this->keys >= $this->getBatchSize() ||
(time() - $this->lastTriggeredTime > $aggregationInterval && $this->keys > 0)
) {
Console::warning('[' . DateTime::now() . '] Aggregated ' . $this->keys . ' keys');
@@ -48,6 +48,7 @@ class StatsUsageDump extends Action
METRIC_BUILDS => true,
METRIC_COLLECTIONS => true,
METRIC_DOCUMENTS => true,
METRIC_DATABASES_STORAGE => true,
];
/**
@@ -63,6 +64,7 @@ class StatsUsageDump extends Action
'.deployments.storage',
'.builds',
'.builds.storage',
'.databases.storage'
];
/**
@@ -117,7 +119,7 @@ class StatsUsageDump extends Action
$project = new Document($stats['project'] ?? []);
$numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
$receivedAt = $stats['receivedAt'] ?? 'NONE';
$receivedAt = $stats['receivedAt'] ?? null;
if ($numberOfKeys === 0) {
continue;
}
@@ -134,7 +136,7 @@ class StatsUsageDump extends Action
if (str_contains($key, METRIC_DATABASES_STORAGE)) {
try {
$this->handleDatabaseStorage($key, $dbForProject, $project);
$this->handleDatabaseStorage($key, $dbForProject, $project, $receivedAt);
} catch (\Exception $e) {
console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
}
@@ -142,7 +144,11 @@ class StatsUsageDump extends Action
}
foreach ($this->periods as $period => $format) {
$time = 'inf' === $period ? null : date($format, time());
$time = null;
if ($period !== 'inf') {
$time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time());
}
$id = \md5("{$time}_{$period}_{$key}");
$document = new Document([
@@ -171,12 +177,12 @@ class StatsUsageDump extends Action
}
}
private function handleDatabaseStorage(string $key, Database $dbForProject, Document $project): void
private function handleDatabaseStorage(string $key, Database $dbForProject, Document $project, string $receivedAt): void
{
$data = explode('.', $key);
$start = microtime(true);
$updateMetric = function (Database $dbForProject, Document $project, int $value, string $key, string $period, string|null $time) {
$updateMetric = function (Database $dbForProject, Document $project, int $value, string $key, string $period, string|null $time) use ($receivedAt) {
$id = \md5("{$time}_{$period}_{$key}");
$document = new Document([
@@ -197,7 +203,11 @@ class StatsUsageDump extends Action
};
foreach ($this->periods as $period => $format) {
$time = 'inf' === $period ? null : date($format, time());
$time = null;
if ($period !== 'inf') {
$time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time());
}
$id = \md5("{$time}_{$period}_{$key}");
$value = 0;
@@ -320,7 +330,7 @@ class StatsUsageDump extends Action
protected function writeToLogsDB(Document $project, Document $document): void
{
if (!System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', false)) {
if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') {
Console::log('Dual Writing is disabled. Skipping...');
return;
}
-292
View File
@@ -1,292 +0,0 @@
<?php
namespace Appwrite\Platform\Workers;
use Appwrite\Event\UsageDump;
use Exception;
use Utopia\CLI\Console;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\System\System;
class Usage extends Action
{
private array $stats = [];
private int $lastTriggeredTime = 0;
private int $aggregationInterval = 20;
private int $keys = 0;
private const INFINITY_PERIOD = '_inf_';
private const KEYS_THRESHOLD = 20000;
public static function getName(): string
{
return 'usage';
}
/**
* @throws Exception
*/
public function __construct()
{
$this
->desc('Usage worker')
->inject('message')
->inject('project')
->inject('getProjectDB')
->inject('queueForUsageDump')
->callback(function (Message $message, Document $project, callable $getProjectDB, UsageDump $queueForUsageDump) {
$this->action($message, $project, $getProjectDB, $queueForUsageDump);
});
$this->aggregationInterval = (int) System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '20');
$this->lastTriggeredTime = time();
}
/**
* @param Message $message
* @param Document $project
* @param callable $getProjectDB
* @param UsageDump $queueForUsageDump
* @return void
* @throws \Utopia\Database\Exception
* @throws Exception
*/
public function action(Message $message, Document $project, callable $getProjectDB, UsageDump $queueForUsageDump): void
{
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
throw new Exception('Missing payload');
}
if (empty($project->getAttribute('database'))) {
var_dump($payload);
return;
}
$projectId = $project->getInternalId();
foreach ($payload['reduce'] ?? [] as $document) {
if (empty($document)) {
continue;
}
$this->reduce(
project: $project,
document: new Document($document),
metrics: $payload['metrics'],
getProjectDB: $getProjectDB
);
}
$this->stats[$projectId]['project'] = [
'$id' => $project->getId(),
'$internalId' => $project->getInternalId(),
'database' => $project->getAttribute('database'),
];
$this->stats[$projectId]['receivedAt'] = DateTime::now();
foreach ($payload['metrics'] ?? [] as $metric) {
$this->keys++;
if (!isset($this->stats[$projectId]['keys'][$metric['key']])) {
$this->stats[$projectId]['keys'][$metric['key']] = $metric['value'];
continue;
}
$this->stats[$projectId]['keys'][$metric['key']] += $metric['value'];
}
// If keys crossed threshold or X time passed since the last send and there are some keys in the array ($this->stats)
if (
$this->keys >= self::KEYS_THRESHOLD ||
(time() - $this->lastTriggeredTime > $this->aggregationInterval && $this->keys > 0)
) {
Console::warning('[' . DateTime::now() . '] Aggregated ' . $this->keys . ' keys');
$queueForUsageDump
->setStats($this->stats)
->trigger();
$this->stats = [];
$this->keys = 0;
$this->lastTriggeredTime = time();
}
}
/**
* On Documents that tied by relations like functions>deployments>build || documents>collection>database || buckets>files.
* When we remove a parent document we need to deduct his children aggregation from the project scope.
* @param Document $project
* @param Document $document
* @param array $metrics
* @param callable $getProjectDB
* @return void
*/
private function reduce(Document $project, Document $document, array &$metrics, callable $getProjectDB): void
{
$dbForProject = $getProjectDB($project);
try {
switch (true) {
case $document->getCollection() === 'users': // users
$sessions = count($document->getAttribute(METRIC_SESSIONS, 0));
if (!empty($sessions)) {
$metrics[] = [
'key' => METRIC_SESSIONS,
'value' => ($sessions * -1),
];
}
break;
case $document->getCollection() === 'databases': // databases
$collections = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getInternalId(), METRIC_DATABASE_ID_COLLECTIONS)));
$documents = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getInternalId(), METRIC_DATABASE_ID_DOCUMENTS)));
if (!empty($collections['value'])) {
$metrics[] = [
'key' => METRIC_COLLECTIONS,
'value' => ($collections['value'] * -1),
];
}
if (!empty($documents['value'])) {
$metrics[] = [
'key' => METRIC_DOCUMENTS,
'value' => ($documents['value'] * -1),
];
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$documents = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $document->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS)));
if (!empty($documents['value'])) {
$metrics[] = [
'key' => METRIC_DOCUMENTS,
'value' => ($documents['value'] * -1),
];
$metrics[] = [
'key' => str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS),
'value' => ($documents['value'] * -1),
];
}
break;
case $document->getCollection() === 'buckets':
$files = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getInternalId(), METRIC_BUCKET_ID_FILES)));
$storage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE)));
if (!empty($files['value'])) {
$metrics[] = [
'key' => METRIC_FILES,
'value' => ($files['value'] * -1),
];
}
if (!empty($storage['value'])) {
$metrics[] = [
'key' => METRIC_FILES_STORAGE,
'value' => ($storage['value'] * -1),
];
}
break;
case $document->getCollection() === 'functions':
$deployments = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $document->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS)));
$deploymentsStorage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $document->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE)));
$builds = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS)));
$buildsSuccess = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_SUCCESS)));
$buildsFailed = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_FAILED)));
$buildsStorage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE)));
$buildsCompute = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE)));
$buildsComputeSuccess = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS)));
$buildsComputeFailed = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED)));
$executions = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS)));
$executionsCompute = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE)));
if (!empty($deployments['value'])) {
$metrics[] = [
'key' => METRIC_DEPLOYMENTS,
'value' => ($deployments['value'] * -1),
];
}
if (!empty($deploymentsStorage['value'])) {
$metrics[] = [
'key' => METRIC_DEPLOYMENTS_STORAGE,
'value' => ($deploymentsStorage['value'] * -1),
];
}
if (!empty($builds['value'])) {
$metrics[] = [
'key' => METRIC_BUILDS,
'value' => ($builds['value'] * -1),
];
}
if (!empty($buildsSuccess['value'])) {
$metrics[] = [
'key' => METRIC_BUILDS_SUCCESS,
'value' => ($buildsSuccess['value'] * -1),
];
}
if (!empty($buildsFailed['value'])) {
$metrics[] = [
'key' => METRIC_BUILDS_FAILED,
'value' => ($buildsFailed['value'] * -1),
];
}
if (!empty($buildsStorage['value'])) {
$metrics[] = [
'key' => METRIC_BUILDS_STORAGE,
'value' => ($buildsStorage['value'] * -1),
];
}
if (!empty($buildsCompute['value'])) {
$metrics[] = [
'key' => METRIC_BUILDS_COMPUTE,
'value' => ($buildsCompute['value'] * -1),
];
}
if (!empty($buildsComputeSuccess['value'])) {
$metrics[] = [
'key' => METRIC_BUILDS_COMPUTE_SUCCESS,
'value' => ($buildsComputeSuccess['value'] * -1),
];
}
if (!empty($buildsComputeFailed['value'])) {
$metrics[] = [
'key' => METRIC_BUILDS_COMPUTE_FAILED,
'value' => ($buildsComputeFailed['value'] * -1),
];
}
if (!empty($executions['value'])) {
$metrics[] = [
'key' => METRIC_EXECUTIONS,
'value' => ($executions['value'] * -1),
];
}
if (!empty($executionsCompute['value'])) {
$metrics[] = [
'key' => METRIC_EXECUTIONS_COMPUTE,
'value' => ($executionsCompute['value'] * -1),
];
}
break;
default:
break;
}
} catch (\Throwable $e) {
console::error("[reducer] " . " {DateTime::now()} " . " {$project->getInternalId()} " . " {$e->getMessage()}");
}
}
}
-286
View File
@@ -1,286 +0,0 @@
<?php
namespace Appwrite\Platform\Workers;
use Appwrite\Extend\Exception;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\System\System;
const METRIC_COLLECTION_LEVEL_STORAGE = 4;
const METRIC_DATABASE_LEVEL_STORAGE = 3;
const METRIC_PROJECT_LEVEL_STORAGE = 2;
class UsageDump extends Action
{
protected array $stats = [];
protected array $periods = [
'1h' => 'Y-m-d H:00',
'1d' => 'Y-m-d 00:00',
'inf' => '0000-00-00 00:00'
];
public static function getName(): string
{
return 'usage-dump';
}
/**
* @throws \Exception
*/
public function __construct()
{
$this
->inject('message')
->inject('getProjectDB')
->callback(function (Message $message, callable $getProjectDB) {
$this->action($message, $getProjectDB);
});
}
/**
* @param Message $message
* @param callable $getProjectDB
* @return void
* @throws Exception
* @throws \Utopia\Database\Exception
*/
public function action(Message $message, callable $getProjectDB): void
{
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
throw new Exception('Missing payload');
}
foreach ($payload['stats'] ?? [] as $stats) {
$project = new Document($stats['project'] ?? []);
/**
* End temp bug fallback
*/
$numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
$receivedAt = $stats['receivedAt'] ?? 'NONE';
if ($numberOfKeys === 0) {
continue;
}
console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
try {
$dbForProject = $getProjectDB($project);
foreach ($stats['keys'] ?? [] as $key => $value) {
if ($value == 0) {
continue;
}
if (str_contains($key, METRIC_DATABASES_STORAGE)) {
try {
$this->handleDatabaseStorage($key, $dbForProject);
} catch (\Exception $e) {
console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
}
continue;
}
foreach ($this->periods as $period => $format) {
$time = 'inf' === $period ? null : date($format, time());
$id = \md5("{$time}_{$period}_{$key}");
try {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $key,
'value' => $value,
'region' => System::getEnv('_APP_REGION', 'default'),
]));
} catch (Duplicate $th) {
if ($value < 0) {
$dbForProject->decreaseDocumentAttribute(
'stats',
$id,
'value',
abs($value)
);
} else {
$dbForProject->increaseDocumentAttribute(
'stats',
$id,
'value',
$value
);
}
}
}
}
} catch (\Exception $e) {
console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
}
}
}
private function handleDatabaseStorage(string $key, Database $dbForProject): void
{
$data = explode('.', $key);
$start = microtime(true);
$updateMetric = function (Database $dbForProject, int $value, string $key, string $period, string|null $time) {
$id = \md5("{$time}_{$period}_{$key}");
try {
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $key,
'value' => $value,
'region' => System::getEnv('_APP_REGION', 'default'),
]));
} catch (Duplicate $th) {
if ($value < 0) {
$dbForProject->decreaseDocumentAttribute(
'stats',
$id,
'value',
abs($value)
);
} else {
$dbForProject->increaseDocumentAttribute(
'stats',
$id,
'value',
$value
);
}
}
};
foreach ($this->periods as $period => $format) {
$time = 'inf' === $period ? null : date($format, time());
$id = \md5("{$time}_{$period}_{$key}");
$value = 0;
$previousValue = 0;
try {
$previousValue = ($dbForProject->getDocument('stats', $id))->getAttribute('value', 0);
} catch (\Exception $e) {
// No previous value
}
switch (count($data)) {
// Collection Level
case METRIC_COLLECTION_LEVEL_STORAGE:
Console::log('[' . DateTime::now() . '] Collection Level Storage Calculation [' . $key . ']');
$databaseInternalId = $data[0];
$collectionInternalId = $data[1];
try {
$value = $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collectionInternalId);
} catch (\Exception $e) {
// Collection not found
if ($e->getMessage() !== 'Collection not found') {
throw $e;
}
}
// Compare with previous value
$diff = $value - $previousValue;
if ($diff === 0) {
break;
}
// Update Collection
$updateMetric($dbForProject, $diff, $key, $period, $time);
// Update Database
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
$updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
// Update Project
$projectKey = METRIC_DATABASES_STORAGE;
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
break;
// Database Level
case METRIC_DATABASE_LEVEL_STORAGE:
Console::log('[' . DateTime::now() . '] Database Level Storage Calculation [' . $key . ']');
$databaseInternalId = $data[0];
$collections = [];
try {
$collections = $dbForProject->find('database_' . $databaseInternalId);
} catch (\Exception $e) {
// Database not found
if ($e->getMessage() !== 'Collection not found') {
throw $e;
}
}
foreach ($collections as $collection) {
try {
$value += $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
} catch (\Exception $e) {
// Collection not found
if ($e->getMessage() !== 'Collection not found') {
throw $e;
}
}
}
$diff = $value - $previousValue;
if ($diff === 0) {
break;
}
// Update Database
$databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
$updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
// Update Project
$projectKey = METRIC_DATABASES_STORAGE;
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
break;
// Project Level
case METRIC_PROJECT_LEVEL_STORAGE:
Console::log('[' . DateTime::now() . '] Project Level Storage Calculation [' . $key . ']');
// Get all project databases
$databases = $dbForProject->find('database');
// Recalculate all databases
foreach ($databases as $database) {
$collections = $dbForProject->find('database_' . $database->getInternalId());
foreach ($collections as $collection) {
try {
$value += $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
} catch (\Exception $e) {
// Collection not found
if ($e->getMessage() !== 'Collection not found') {
throw $e;
}
}
}
}
$diff = $value - $previousValue;
// Update Project
$projectKey = METRIC_DATABASES_STORAGE;
$updateMetric($dbForProject, $diff, $projectKey, $period, $time);
break;
}
}
$end = microtime(true);
console::log('[' . DateTime::now() . '] DB Storage Calculation [' . $key . '] took ' . (($end - $start) * 1000) . ' milliseconds');
}
}
@@ -24,7 +24,15 @@ class Base extends Queries
public function __construct(string $collection, array $allowedAttributes)
{
$config = Config::getParam('collections', []);
$collections = array_merge($config['projects'], $config['buckets'], $config['databases'], $config['console']);
$collections = array_merge(
$config['projects'],
$config['buckets'],
$config['databases'],
$config['console'],
$config['logs']
);
$collection = $collections[$collection];
// array for constant lookup time
$allowedAttributesLookup = [];
@@ -35,6 +43,7 @@ class Base extends Queries
$attributes = [];
foreach ($collection['attributes'] as $attribute) {
$key = $attribute['$id'];
if (!isset($allowedAttributesLookup[$key])) {
continue;
}
@@ -42,6 +42,19 @@ class UsageBuckets extends Model
'example' => [],
'array' => true
])
->addRule('imageTransformations', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of files transformations per period.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('imageTransformationsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of files transformations.',
'default' => 0,
'example' => 0,
])
;
}
@@ -197,6 +197,19 @@ class UsageProject extends Model
'example' => [],
'array' => true
])
->addRule('imageTransformations', [
'type' => Response::MODEL_METRIC,
'description' => 'An array of aggregated number of image transformations.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('imageTransformationsTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of image transformations.',
'default' => 0,
'example' => 0,
])
;
}
+35 -59
View File
@@ -23,7 +23,7 @@ class UsageTest extends Scope
use SideServer;
use FunctionsBase;
private const WAIT = 35;
private const WAIT = 5;
private const CREATE = 20;
protected string $projectId;
@@ -134,8 +134,6 @@ class UsageTest extends Scope
#[Retry(count: 1)]
public function testUsersStats(array $data): array
{
sleep(self::WAIT);
$requestsTotal = $data['requestsTotal'];
$response = $this->client->call(
@@ -150,7 +148,7 @@ class UsageTest extends Scope
);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(29, count($response['body']));
$this->assertEquals(31, count($response['body']));
$this->validateDates($response['body']['network']);
$this->validateDates($response['body']['requests']);
$this->validateDates($response['body']['users']);
@@ -309,7 +307,7 @@ class UsageTest extends Scope
/**
* @depends testPrepareStorageStats
*/
#[Retry(count: 1)]
#[Retry(count: 10)]
public function testStorageStats(array $data): array
{
$bucketId = $data['bucketId'];
@@ -318,8 +316,6 @@ class UsageTest extends Scope
$storageTotal = $data['storageTotal'];
$filesTotal = $data['filesTotal'];
sleep(self::WAIT);
$response = $this->client->call(
Client::METHOD_GET,
'/project/usage',
@@ -331,7 +327,7 @@ class UsageTest extends Scope
]
);
$this->assertEquals(29, count($response['body']));
$this->assertEquals(31, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
$this->validateDates($response['body']['requests']);
@@ -474,10 +470,10 @@ class UsageTest extends Scope
$this->assertEquals('name', $response['body']['key']);
$requestsTotal += 1;
sleep(self::WAIT);
$requestsTotal += 1;
for ($i = 0; $i < self::CREATE; $i++) {
$name = uniqid() . ' collection';
@@ -552,7 +548,7 @@ class UsageTest extends Scope
]
);
$this->assertEquals(29, count($response['body']));
$this->assertEquals(31, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals(1, count($response['body']['network']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
@@ -709,8 +705,6 @@ class UsageTest extends Scope
// $this->assertEquals(201, $response['headers']['status-code']);
// }
// sleep(self::WAIT);
// for ($i = 0; $i < 3; $i++) {
// try {
// $newProjectMetrics = $this->client->call(
@@ -752,7 +746,6 @@ class UsageTest extends Scope
// if ($i === 2) {
// throw $e;
// }
// sleep(self::WAIT);
// continue;
// }
// }
@@ -792,8 +785,6 @@ class UsageTest extends Scope
// $this->assertEquals(204, $response['headers']['status-code']);
// }
// sleep(self::WAIT);
// for ($i = 0; $i < 3; $i++) {
// try {
// $newProjectMetrics = $this->client->call(
@@ -835,7 +826,6 @@ class UsageTest extends Scope
// if ($i === 2) {
// throw $e;
// }
// sleep(self::WAIT);
// continue;
// }
// }
@@ -1027,8 +1017,6 @@ class UsageTest extends Scope
$executionTime = $data['executionTime'];
$executions = $data['executions'];
sleep(self::WAIT);
$response = $this->client->call(
Client::METHOD_GET,
'/functions/' . $functionId . '/usage?range=30d',
@@ -1152,52 +1140,40 @@ class UsageTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
sleep(self::WAIT + 20);
$tries = 0;
while (true) {
try {
// Compare new values with old values
$response = $this->client->call(
Client::METHOD_GET,
'/functions/' . $functionId . '/usage?range=30d',
$this->getConsoleHeaders()
);
$this->assertEventually(function () use ($functionId, $functionsMetrics, $projectMetrics) {
// Compare new values with old values
$response = $this->client->call(
Client::METHOD_GET,
'/functions/' . $functionId . '/usage?range=30d',
$this->getConsoleHeaders()
);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(19, count($response['body']));
$this->assertEquals('30d', $response['body']['range']);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(19, count($response['body']));
$this->assertEquals('30d', $response['body']['range']);
// Check if the new values are greater than the old values
$this->assertEquals($functionsMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
$this->assertGreaterThan($functionsMetrics['executionsTimeTotal'], $response['body']['executionsTimeTotal']);
$this->assertGreaterThan($functionsMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
// Check if the new values are greater than the old values
$this->assertEquals($functionsMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
$this->assertGreaterThan($functionsMetrics['executionsTimeTotal'], $response['body']['executionsTimeTotal']);
$this->assertGreaterThan($functionsMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
$response = $this->client->call(
Client::METHOD_GET,
'/project/usage',
$this->getConsoleHeaders(),
[
'period' => '1h',
'startDate' => self::getToday(),
'endDate' => self::getTomorrow(),
]
);
$response = $this->client->call(
Client::METHOD_GET,
'/project/usage',
$this->getConsoleHeaders(),
[
'period' => '1h',
'startDate' => self::getToday(),
'endDate' => self::getTomorrow(),
]
);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($projectMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
$this->assertGreaterThan($projectMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
break;
} catch (ExpectationFailedException $th) {
if ($tries >= 5) {
throw $th;
} else {
$tries++;
sleep(5);
}
}
}
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($projectMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
$this->assertGreaterThan($projectMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
});
}
public function tearDown(): void
@@ -277,6 +277,7 @@ class AccountCustomClientTest extends Scope
{
sleep(5);
$session = $data['session'] ?? '';
/**
* Test for SUCCESS
*/
@@ -2418,6 +2419,33 @@ class AccountCustomClientTest extends Scope
$message = $smsRequest['data']['message'];
$token = substr($message, 0, 6);
/**
* Test for FAILURE
*/
// disable phone sessions
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/phone', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
]), [
'status' => false,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(false, $response['body']['authPhone']);
$response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]));
$this->assertEquals(501, $response['headers']['status-code']);
$this->assertEquals("Phone authentication is disabled for this project", $response['body']['message']);
return \array_merge($data, [
'token' => \substr($smsRequest['data']['message'], 0, 6)
]);
@@ -2309,6 +2309,30 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(100, $new['body']['min']);
$this->assertEquals(2000, $new['body']['max']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 100,
'min' => 0,
]);
$this->assertEquals(200, $update['headers']['status-code']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 10,
'max' => 100,
]);
$this->assertEquals(200, $update['headers']['status-code']);
/**
* Test against failure
*/
@@ -2368,32 +2392,6 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 100,
'min' => 0,
]);
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 100,
'max' => 0,
]);
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2572,6 +2570,30 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(123.456, $new['body']['min']);
$this->assertEquals(2000, $new['body']['max']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 123.456,
'min' => 0.0,
]);
$this->assertEquals(200, $update['headers']['status-code']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 23.456,
'max' => 100.0,
]);
$this->assertEquals(200, $update['headers']['status-code']);
/**
* Test against failure
*/
@@ -2631,32 +2653,6 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 123.456,
'min' => 0.0,
]);
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'required' => false,
'default' => 123.456,
'max' => 0.0,
]);
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -13,7 +13,6 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\System\System;
class FunctionsCustomServerTest extends Scope
{
@@ -1686,9 +1685,6 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(1, count($executions['body']['executions']));
});
// Await Aggregation
sleep(System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30));
$this->assertEventually(function () use ($functionId) {
$response = $this->getFunctionUsage($functionId, [
'range' => '24h'
@@ -53,8 +53,9 @@ trait MigrationsBase
$this->assertNotEmpty($migration['body']);
$this->assertNotEmpty($migration['body']['$id']);
$attempts = 0;
while ($attempts < 5) {
$migrationResult = [];
$this->assertEventually(function () use ($migration, &$migrationResult) {
$response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migration['body']['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getDestinationProject()['$id'],
@@ -66,24 +67,18 @@ trait MigrationsBase
$this->assertNotEmpty($response['body']['$id']);
if ($response['body']['status'] === 'failed') {
$this->fail('Migration failed', json_encode($response['body'], JSON_PRETTY_PRINT));
$this->fail('Migration failed' . json_encode($response['body'], JSON_PRETTY_PRINT));
}
$this->assertNotEquals('failed', $response['body']['status']);
$this->assertEquals('completed', $response['body']['status']);
if ($response['body']['status'] === 'completed') {
return $response['body'];
}
$migrationResult = $response['body'];
if ($attempts === 4) {
$this->assertEquals('completed', $response['body']['status']);
}
return true;
});
$attempts++;
sleep(5);
}
return [];
return $migrationResult;
}
/**
@@ -1312,22 +1312,15 @@ class RealtimeCustomClientTest extends Scope
$this->assertNotEmpty($deployment['body']['$id']);
// Poll until deployment is built
while (true) {
$this->assertEventually(function () use ($function, $deploymentId) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
if (
$deployment['headers']['status-code'] >= 400
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
) {
break;
}
\sleep(1);
}
$this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body']));
});
$response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
@@ -98,11 +98,13 @@ class StorageConsoleClientTest extends Scope
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(5, count($response['body']));
$this->assertEquals(7, count($response['body']));
$this->assertEquals('24h', $response['body']['range']);
$this->assertIsNumeric($response['body']['filesTotal']);
$this->assertIsNumeric($response['body']['filesStorageTotal']);
$this->assertIsArray($response['body']['files']);
$this->assertIsArray($response['body']['storage']);
$this->assertIsArray($response['body']['imageTransformations']);
$this->assertIsNumeric($response['body']['imageTransformationsTotal']);
}
}
+6 -14
View File
@@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Webhooks;
use Appwrite\Tests\Async;
use Appwrite\Tests\Retry;
use CURLFile;
use Tests\E2E\Client;
@@ -12,29 +13,20 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
trait WebhooksBase
{
protected function awaitDeploymentIsBuilt($functionId, $deploymentId, $checkForSuccess = true): void
use Async;
protected function awaitDeploymentIsBuilt($functionId, $deploymentId): void
{
while (true) {
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
if (
$deployment['headers']['status-code'] >= 400
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
) {
break;
}
\sleep(1);
}
if ($checkForSuccess) {
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body']));
}
});
}
public static function getWebhookSignature(array $webhook, string $signatureKey): string