Compare commits

...
Author SHA1 Message Date
Jake Barnby 7a3ea14abf Merge remote-tracking branch 'origin/1.6.x' into fix-dead-connections
# Conflicts:
#	app/controllers/general.php
#	app/http.php
#	composer.json
#	composer.lock
2025-02-18 00:49:33 +13:00
Jake BarnbyandGitHub d1cf833f15 Merge pull request #9375 from appwrite/feat-worker-batch-delete
Use batch delete in worker
2025-02-18 00:08:26 +13:00
Jake Barnby 1fbfbbf482 Also update database worker delete by group 2025-02-17 23:53:43 +13:00
Jake Barnby ef8520ad2d Format 2025-02-17 23:14:17 +13:00
Jake Barnby 36ce381325 Use batch delete in worker 2025-02-17 22:36:55 +13:00
Damodar LohaniandGitHub b3067f562f Merge pull request #9370 from appwrite/fix-image-trnasformation-metrics
Fix image trnasformation metrics
2025-02-16 17:29:13 +05:45
Damodar Lohani 3e5e545304 Fix: image transformations metrics 2025-02-16 11:31:46 +00:00
Steven NguyenandGitHub 993c07d7c6 Merge pull request #9360 from appwrite/fix-build-memory-limits
Fix build memory specifications
2025-02-14 07:36:34 -08:00
Bradley Schofield 0f873792ba Fix build memory and add tests to prevent future regressions 2025-02-14 17:16:40 +09:00
Jake BarnbyandGitHub 7143bef111 Merge pull request #9356 from appwrite/catch-invalid-index
Fix patch index validation
2025-02-13 22:56:28 +13:00
fogelito e4ae0d463d assertStringContainsString 2025-02-13 11:37:12 +02:00
fogelito 8778e2e666 lint 2025-02-13 11:35:16 +02:00
fogelito 8f212ccd30 in_array 2025-02-13 11:24:13 +02:00
fogelito e8a306253b Fix test 2025-02-13 11:22:40 +02:00
fogelito 4592a1cc36 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into catch-invalid-index 2025-02-13 11:20:52 +02:00
Christy JacobandGitHub 608a2cc67b Merge pull request #9354 from appwrite/fix-image-transformation-metric-time
Fix: metric time was not being written to DB
2025-02-13 14:41:52 +05:30
fogelito 6abf8ce727 Fix index length test 2025-02-13 10:44:27 +02:00
Jake BarnbyandGitHub ed51a059ba Merge pull request #9355 from appwrite/chore-update-migrations
Chore: Update migrations
2025-02-13 20:59:33 +13:00
Bradley Schofield 43d8374c5c Update migrations 2025-02-13 16:57:32 +09:00
Damodar Lohani d4599e6884 Fix, time was not being written to DB 2025-02-13 06:43:49 +00:00
Christy JacobandGitHub 88c2fe2dd2 Merge pull request #9347 from appwrite/update-audit-logging
feat: batch create abuse logs
2025-02-12 22:35:34 +05:30
Christy Jacob a0482aca7a chore: revert package.json 2025-02-12 22:04:48 +05:30
Christy Jacob 9989137ed4 chore: use private variables for aggregation 2025-02-12 22:01:28 +05:30
Christy JacobandGitHub 933e762284 Merge pull request #9332 from appwrite/PLA-896
feat(builds): check if function is blocked before building
2025-02-12 21:23:10 +05:30
Damodar LohaniandGitHub 8922d676c6 Merge pull request #9350 from appwrite/fix-image-metric
Fix: missing periodic metric
2025-02-12 17:25:15 +05:45
Damodar Lohani d2cc5ea932 Fix: missing periodic metric 2025-02-12 10:58:51 +00:00
Jake BarnbyandGitHub 4c452417d1 Merge pull request #9338 from appwrite/feat-improve-tests
Only run all table mode tests on db update
2025-02-12 22:34:01 +13:00
Christy Jacob 2dffcb29b0 chore: add comment about the unit 2025-02-12 13:09:31 +05:30
Jake BarnbyandGitHub dd03a04d58 Merge branch '1.6.x' into feat-improve-tests 2025-02-12 20:35:13 +13:00
Christy Jacob 294f6818fa chore: review comments 2025-02-12 12:23:20 +05:30
Christy Jacob 48525ce755 chore: linter 2025-02-12 11:34:39 +05:30
Christy Jacob 978284b36f feat: use smaller batch size on development environments 2025-02-12 11:33:17 +05:30
Christy Jacob 7f706fce5d feat: linter & console logs 2025-02-12 11:09:24 +05:30
Christy Jacob df27e8950b feat: update composer 2025-02-12 11:02:29 +05:30
Christy Jacob 00473d4744 feat: batch create abuse logs 2025-02-12 10:57:43 +05:30
Jake BarnbyandGitHub c22ece068f Merge pull request #9346 from appwrite/fix-drop-abuse
Fix drop abuse on shared table project delete
2025-02-12 17:41:40 +13:00
Jake Barnby e394532bcd Fix drop abuse on shared table project delete 2025-02-12 17:03:37 +13:00
Christy JacobandGitHub e140603890 Merge pull request #9342 from appwrite/lohanidamodar-patch-2
Fix: missing call for image transformations counting
2025-02-11 22:13:15 +05:30
Christy Jacob 2189ca1469 chore: linter 2025-02-11 16:40:17 +00:00
Damodar LohaniandGitHub f6aff786f1 Fix: missing call for image transformations counting 2025-02-11 17:59:48 +05:45
Fabian Gruber 8324b789c5 chore(review): remove http status codes from exceptions 2025-02-11 11:00:28 +01:00
Fabian Gruber f2ae6e07cb feat(builds): check if function is blocked before building 2025-02-11 10:45:26 +01:00
Jake Barnby 61e65e1eda Skip shared jobs entirely 2025-02-11 22:37:57 +13:00
Jake Barnby abaa5c7f48 Only run shared tables tests if database library version changed compared to base 2025-02-11 22:26:39 +13:00
Jake BarnbyandGitHub 2902b1bde8 Merge pull request #9329 from appwrite/catch-invalid-index
Catch invalid index
2025-02-11 20:34:10 +13:00
Christy JacobandGitHub 2f95b0e2e0 Merge pull request #9272 from appwrite/feat-logs-db
Feat: logs DB
2025-02-10 23:49:48 +05:30
Christy Jacob bfc0b53163 chore: add simple toggle for dual writing 2025-02-10 23:05:34 +05:30
fogelito c77ca64fc0 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into catch-invalid-index 2025-02-10 15:41:50 +02:00
fogelito 8c3a243a5d Typo 2025-02-10 15:40:45 +02:00
fogelito 7a8688932c Add test 2025-02-10 15:40:21 +02:00
Steven NguyenandGitHub 06e857e5d9 Merge pull request #9321 from appwrite/pla-2453
Fix WEBP File Preview Rendering Issue
2025-02-10 06:33:40 -07:00
fogelito 7caab2f9ff IndexException 2025-02-10 10:55:37 +02:00
fogelito 33f55c73a7 Revert cast Index 2025-02-10 10:42:29 +02:00
fogelito edbe0fb72f Bump Database version 0.58.5 2025-02-10 10:40:12 +02:00
fogelito 23a9b519f6 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into catch-invalid-index 2025-02-10 10:37:05 +02:00
fogelito a032540b3c IndexException 2025-02-10 10:11:52 +02:00
fogelito 27709e7683 IndexException 2025-02-10 10:04:51 +02:00
fogelito 5b786e2a68 Catch index exception 2025-02-10 09:19:56 +02:00
Christy JacobandGitHub 42a4fe2efc Merge branch '1.6.x' into feat-logs-db 2025-02-10 10:18:09 +05:30
Christy JacobandGitHub 1c6bce0e79 Merge pull request #9330 from appwrite/update-queue-library
chore: update dependencies
2025-02-10 09:54:56 +05:30
Christy Jacob f6c28322c9 chore: update dependencies 2025-02-10 09:30:24 +05:30
fogelito 3092e765cc indexException 2025-02-09 18:34:41 +02:00
Damodar Lohani e8faf2a859 fix worker 2025-02-09 08:52:07 +00:00
Damodar Lohani ec9045b488 add stats-resources health queue 2025-02-09 06:36:59 +00:00
55e30a0ef4 Update src/Appwrite/Platform/Workers/StatsUsageDump.php
Co-authored-by: Christy Jacob <christyjacob4@gmail.com>
2025-02-09 12:19:16 +05:45
Damodar Lohani abd899186d Merge remote-tracking branch 'origin/1.6.x' into feat-logs-db 2025-02-09 05:20:10 +00:00
Damodar Lohani 59516408af revert env update 2025-02-09 05:15:26 +00:00
Christy JacobandGitHub 859d556461 Merge pull request #9323 from appwrite/revert-9320-revert-9319-revert-9262-fix-redirect-validator-v2
Revert "Restore "feat: custom app schemes"""
2025-02-07 23:09:36 +05:30
Luke B. SilverandGitHub 975393ae48 Revert "Restore "feat: custom app schemes""" 2025-02-07 17:34:58 +00:00
Christy JacobandGitHub 0c096b7434 Merge pull request #9320 from appwrite/revert-9319-revert-9262-fix-redirect-validator-v2
Restore "feat: custom app schemes""
2025-02-07 21:40:13 +05:30
loks0n 3d74b651fe test: subdomain case 2025-02-07 15:49:15 +00:00
loks0n 111c28592d chore: fix warning 2025-02-07 15:22:02 +00:00
loks0n 10ab613f3b chore: fix empty schemes 2025-02-07 15:15:54 +00:00
loks0n 10b88ba194 chore: small improvements 2025-02-07 10:38:36 +00:00
Chirag Aggarwal 768e4c4988 chore: added webp to allowed file inputs 2025-02-07 10:36:41 +00:00
Luke B. SilverandGitHub 9788a126a8 Revert "Revert "feat: custom app schemes"" 2025-02-07 09:53:26 +00:00
Christy JacobandGitHub ad56994b83 Merge pull request #9319 from appwrite/revert-9262-fix-redirect-validator-v2
Revert "feat: custom app schemes"
2025-02-07 15:23:05 +05:30
Luke B. SilverandGitHub 2af026eabe Revert "feat: custom app schemes" 2025-02-07 09:51:27 +00:00
Steven NguyenandGitHub a7b1363644 Merge pull request #9262 from appwrite/fix-redirect-validator-v2
feat: custom app schemes
2025-02-06 18:13:03 -08:00
Steven NguyenandGitHub 1d55bbc0d7 Merge pull request #9271 from appwrite/pla-2009
chore: override getUserAgent function to set forwarded user agent
2025-02-06 17:51:49 -08:00
Steven NguyenandGitHub 1f8448a514 Merge pull request #9228 from ChiragAgg5k/fix-slack-oauth
fix: slack oauth scopes, and updated to v2
2025-02-06 17:22:54 -08:00
Christy JacobandGitHub 7220abe9a4 Merge pull request #9313 from appwrite/queue-0.8.1
fix(schedule-tasks): revert back to direct pool usage
2025-02-06 18:08:42 +05:30
Fabian Gruber 273ea0abcc fix(schedule-tasks): revert back to direct pool usage 2025-02-06 13:18:18 +01:00
loks0n a4c8b74501 chore: revert storage 2025-02-06 11:24:29 +00:00
loks0n b69e78e5bf Merge branch '1.6.x' of https://github.com/appwrite/appwrite into fix-redirect-validator-v2 2025-02-06 11:22:49 +00:00
Christy JacobandGitHub c46c5e4259 Merge pull request #9312 from appwrite/queue-0.8.1
chore: update utopia-php/queue to 0.8.2
2025-02-06 16:34:20 +05:30
Fabian Gruber c02a8d3997 chore: update utopia-php/queue to 0.8.2 2025-02-06 12:02:06 +01:00
Damodar LohaniandGitHub 9e988747d8 Merge branch '1.6.x' into feat-logs-db 2025-02-06 16:02:41 +05:45
Christy JacobandGitHub ec9b609817 Merge pull request #9311 from appwrite/queue-0.8.1
chore: update utopia-php/queue to 0.8.1
2025-02-06 15:11:11 +05:30
Fabian Gruber bb696a6c7b chore: update utopia-php/queue to 0.8.1 2025-02-06 10:38:36 +01:00
Damodar Lohani cd45177558 fix pool empty 2025-02-06 08:06:15 +00:00
Damodar Lohani f6b2d9d937 remove old collections - we only need it in production temporarily 2025-02-06 07:02:07 +00:00
Damodar Lohani c48146b699 fix builds worker 2025-02-06 06:05:53 +00:00
Damodar Lohani c27c3499aa fix endpoint and add test 2025-02-06 04:48:40 +00:00
Damodar Lohani 0a6ebe6661 Merge remote-tracking branch 'origin/1.6.x' into feat-logs-db 2025-02-06 04:40:05 +00:00
Damodar Lohani ae9d3f4703 fix dual writing 2025-02-06 04:40:01 +00:00
Damodar Lohani 2efe1ed9dc new health endpoint 2025-02-06 04:24:04 +00:00
Damodar Lohani c3495dde35 fix key 2025-02-06 04:17:49 +00:00
Damodar Lohani 8da5bfccb6 fix format and test 2025-02-06 03:58:46 +00:00
Damodar Lohani 89aa61b17c fix duplicate constant 2025-02-06 03:37:01 +00:00
Damodar Lohani 87b3f535bd fix renaming 2025-02-06 03:36:53 +00:00
Christy JacobandGitHub a65b0bb32c Merge pull request #9309 from appwrite/fix-certificate-worker-events
fix(certificate worker): events are published without queue name
2025-02-05 23:15:46 +05:30
Fabian Gruber 78dd753e8f fix(certificate worker): events are published without queue name 2025-02-05 18:01:55 +01:00
Christy JacobandGitHub 0ae9a439ee Merge pull request #9308 from appwrite/gql-test-assert-eventually
fix(test): use assertEventually instead of while(true)
2025-02-05 18:32:26 +05:30
Fabian Gruber 213e16050b fix(test): use assertEventually instead of while(true) 2025-02-05 13:40:13 +01:00
Damodar Lohani 68c3aa3078 update to use publisher 2025-02-05 09:53:29 +00:00
Damodar Lohani 3e778c0094 update environment variable 2025-02-05 09:53:23 +00:00
Damodar Lohani cf66bee925 Merge remote-tracking branch 'origin/1.6.x' into feat-logs-db 2025-02-05 09:49:11 +00:00
Christy JacobandGitHub 1adb1e23a9 Merge pull request #9287 from appwrite/amqp
feat: add AMQP queues
2025-02-05 15:01:42 +05:30
Fabian Gruber a16f26d378 feat: add AMQP queues 2025-02-05 10:29:33 +01:00
Damodar Lohani 966fc47e25 fix classname 2025-02-05 09:28:44 +00:00
Damodar Lohani 99f23ff25a refactor 2025-02-05 09:27:36 +00:00
Damodar Lohani 6b930129e5 add DAU and WAU metric 2025-02-05 09:26:00 +00:00
Damodar Lohani 292e859133 rename metric 2025-02-05 09:17:27 +00:00
Damodar Lohani 5052fd8528 get latest usageDump and dual write 2025-02-05 09:11:03 +00:00
Damodar Lohani 5b91b5cec1 env to configure dual write 2025-02-05 09:10:45 +00:00
Damodar Lohani 4d43b41482 Merge remote-tracking branch 'origin/1.6.x' into feat-logs-db 2025-02-05 08:17:46 +00:00
Jake BarnbyandGitHub 6bdab7213a Merge pull request #9306 from appwrite/chore-update-database
Chore update database
2025-02-05 20:20:35 +13:00
Bradley Schofield c86afb9933 Update DB Lib 2025-02-05 14:38:16 +09:00
Bradley Schofield b4e61f8587 Merge branch '1.6.x' into chore-update-database 2025-02-05 14:35:31 +09:00
Bradley Schofield fe5b9c9525 Update DB Dependents 2025-02-05 14:35:20 +09:00
Christy JacobandGitHub 5d243de029 Merge pull request #9269 from ChiragAgg5k/pla-2061
chore: added teamName to membership redirect url
2025-02-04 13:07:25 +05:30
Chirag AggarwalandGitHub deeabc2d86 Merge branch '1.6.x' into pla-2009 2025-02-04 10:44:52 +05:30
Chirag Aggarwal a226e2ce79 chore: remove console user access 2025-02-04 05:14:37 +00:00
Chirag Aggarwal e4122e236c chore: added null coalescing operator 2025-02-04 04:59:18 +00:00
Jake BarnbyandGitHub e1912c621a Merge pull request #9298 from appwrite/fix-projects-tests
Make sessions limit tests assert eventually
2025-02-03 14:46:31 +13:00
Bradley Schofield bbc527d4a4 Use defaults in assert eventually 2025-02-03 10:19:42 +09:00
Bradley Schofield 50cc28647d Make sessions limit tests assert eventually 2025-02-03 09:53:00 +09:00
Damodar LohaniandGitHub 63c07a456f Merge branch '1.6.x' into feat-logs-db 2025-02-02 07:08:25 +05:45
loks0n 58086fbc74 fix: leftover 2025-01-31 22:15:22 +00:00
loks0n 2e8b8367ed fix: localhost case 2025-01-31 22:01:41 +00:00
loks0n ade25ec845 fix: validator edge case 2025-01-30 18:16:07 +00:00
loks0n 0be400da19 fix: validator description 2025-01-30 17:13:09 +00:00
loks0n 8ab06d053f chore: refactor, dbless approach 2025-01-30 16:58:42 +00:00
loks0n dada8f9bc1 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into fix-redirect-validator-v2 2025-01-30 16:00:50 +00:00
Damodar Lohani 978f22b56d rename transformed metric 2025-01-30 07:15:24 +00:00
Damodar Lohani 71c265d4e8 fix wrong comment 2025-01-30 07:12:15 +00:00
Damodar Lohani d31a358392 Temp: everything to be removed 2025-01-30 07:07:10 +00:00
Damodar Lohani 95845f8c0e update env name 2025-01-30 06:42:57 +00:00
Damodar Lohani 6eb4d3f021 reset dockerfile 2025-01-30 06:40:49 +00:00
Damodar Lohani 074f4417ff image transformations metrics and batch writes 2025-01-30 06:24:10 +00:00
Damodar Lohani da7a234038 update 2025-01-30 05:28:45 +00:00
Damodar Lohani f94d1a5d82 update compose.phtml 2025-01-30 05:24:30 +00:00
Chirag Aggarwal 3a5342ae24 chore: added test cases 2025-01-30 05:05:26 +00:00
Damodar Lohani d0724812d7 fix 2025-01-30 05:05:05 +00:00
Damodar Lohani 6fedefdeda fix 2025-01-30 05:03:41 +00:00
Damodar Lohani cb31e7954e refactor rename 2025-01-30 04:53:53 +00:00
Damodar Lohani 0da3f870f9 refactor: rename 2025-01-30 04:43:16 +00:00
Chirag Aggarwal 6801a46d4e chore: remove unnecessary priviledge check 2025-01-30 04:36:51 +00:00
Damodar Lohani 469dbf0a31 reset usage 2025-01-30 04:33:42 +00:00
Damodar Lohani 3fde465dfa Update stats resources task container 2025-01-30 04:15:03 +00:00
Jake BarnbyandGitHub 41d1af273e Merge pull request #9291 from appwrite/feat-migrations-dynamic-keys
Make migrations use Dynamic keys for destination
2025-01-30 17:12:25 +13:00
Damodar Lohani c2ce273065 update stats resources task to use new queue 2025-01-30 04:10:35 +00:00
Damodar Lohani a05d774370 queue resources to cli 2025-01-30 04:07:44 +00:00
Damodar Lohani e78020d45a stats resources evenet 2025-01-30 04:06:57 +00:00
Damodar Lohani 22ab23d009 feat: Stats resources worker 2025-01-30 04:05:51 +00:00
Damodar Lohani 31ddb6e48f rename collection 2025-01-30 03:51:17 +00:00
Damodar Lohani 21097041f0 refactor: rename usage count to stats resources 2025-01-30 03:50:27 +00:00
Bradley Schofield a4fa0e020a Readd Database Scopes 2025-01-30 12:38:19 +09:00
Bradley Schofield 9f043a1b58 Merge branch '1.6.x' into feat-migrations-dynamic-keys 2025-01-30 11:26:52 +09:00
Bradley Schofield ecaaa4be53 Make migrations use Dynamic keys for destination 2025-01-30 11:23:39 +09:00
loks0n 850bed904a chore: improve validator 2025-01-29 17:52:06 +00:00
loks0n 0457d4d052 Merge branch 'fix-redirect-validator-v2' of https://github.com/appwrite/appwrite into fix-redirect-validator-v2 2025-01-29 17:34:53 +00:00
loks0n 84f3e62908 chore: review changes 2025-01-29 17:34:45 +00:00
Christy JacobandGitHub 505a072bdd Merge pull request #9289 from appwrite/add-transformedAt
Add transformed at
2025-01-29 22:55:26 +05:30
shimonandChristy Jacob 6ef49c8f73 transformedAt for files addition 2025-01-29 22:12:35 +05:30
shimonandChristy Jacob 850737c953 transformedAt for files addition 2025-01-29 22:12:31 +05:30
shimonandChristy Jacob a12fce550a payload debug 2025-01-29 22:12:24 +05:30
Christy JacobandGitHub 3d9653a3df Merge pull request #9284 from appwrite/revert-9281-transformedAt-addition
Revert "Transformed at addition"
2025-01-29 14:05:37 +05:30
Shimon NewmanandGitHub f1f660352e Revert "Transformed at addition" 2025-01-29 10:30:35 +02:00
Christy JacobandGitHub 0220144615 Merge pull request #9282 from appwrite/update-sdk-gen
Update sdk generator dependency
2025-01-29 10:27:43 +05:30
Chirag AggarwalandGitHub 2930c32184 Merge branch '1.6.x' into fix-slack-oauth 2025-01-29 09:39:14 +05:30
DarshanandGitHub dbfb158452 Merge branch '1.6.x' into update-sdk-gen 2025-01-29 09:38:10 +05:30
Darshan ccc800762c update: sdk generator dependency. 2025-01-29 09:36:38 +05:30
Steven NguyenandGitHub 8e42b5c97e Merge pull request #9236 from appwrite/docs-clarify-updates-endpoints-2
Docs: clarify update endpoints only work on draft messages
2025-01-28 13:59:14 -08:00
Ebenezer Don 2e438edcc1 regenerate specs 2025-01-28 21:36:42 +00:00
Ebenezer Don 6394a30581 Merge branch '1.6.x' into docs-clarify-updates-endpoints-2 2025-01-28 21:34:08 +00:00
Eldad A. FuxandGitHub e70de37c0b Merge pull request #9281 from appwrite/transformedAt-addition
Transformed at addition
2025-01-28 16:30:32 +01:00
shimon 34f6dab14e transformedAt for files addition 2025-01-28 16:52:04 +02:00
shimon f97b3aaa86 transformedAt for files addition 2025-01-28 16:49:55 +02:00
Eldad A. FuxandGitHub 71013cab08 Merge pull request #9280 from appwrite/update-sdk-gen
Update Sdk Generator dependency
2025-01-28 15:09:43 +01:00
shimon 163f7b8d17 Merge branch '1.6.x' of github.com:appwrite/appwrite into 1.6.x 2025-01-28 16:07:30 +02:00
Darshan 63c841b7a8 update: sdk generator dependency. 2025-01-28 19:37:27 +05:30
Luke B. SilverandGitHub f95504f829 Merge branch '1.6.x' into fix-redirect-validator-v2 2025-01-28 13:34:18 +00:00
Christy JacobandGitHub 425e0c9a6a Merge pull request #9263 from appwrite/add-fast2sms-adapter
feat: add fast2SMS adapter
2025-01-28 16:02:55 +05:30
Christy Jacob 8cf6a4f393 chore: update deps 2025-01-28 15:58:16 +05:30
Christy Jacob 2307160238 Merge branch '1.6.x' of github.com:appwrite/appwrite into add-fast2sms-adapter 2025-01-28 15:57:53 +05:30
Christy JacobandGitHub 2cfa44faba Merge pull request #9276 from appwrite/revert-9255-feat-batch-usage-dump
Revert "Feat batch usage dump"
2025-01-28 15:29:55 +05:30
Jake BarnbyandGitHub 76680bcbcc Revert "Feat batch usage dump" 2025-01-28 22:29:24 +13:00
Christy Jacob 419f3bee1d chore: fix tests 2025-01-28 13:32:10 +05:30
Christy Jacob 8fdf574a69 Merge branch '1.6.x' of github.com:appwrite/appwrite into add-fast2sms-adapter 2025-01-28 13:18:36 +05:30
Damodar Lohani fb5aeb1476 skip some metrics override 2025-01-28 07:19:27 +00:00
Christy Jacob 5593682c3d chore: review comments 2025-01-28 12:24:16 +05:30
Christy Jacob 8ceb6e2c25 chore: review comments 2025-01-28 12:23:18 +05:30
Damodar Lohani d84f6e4ee0 reset database listeners 2025-01-28 06:40:20 +00:00
Christy Jacob 6cef099890 chore: linter 2025-01-28 11:51:11 +05:30
Christy Jacob dfbe3220d8 chore: linter 2025-01-28 11:49:29 +05:30
Christy Jacob bbe52db4ff feat: add geosms adapter 2025-01-28 11:29:15 +05:30
Damodar Lohani a85499a4ec Merge remote-tracking branch 'origin/1.6.x' into feat-logs-db 2025-01-28 02:11:01 +00:00
loks0n 46532671d6 chore: specs 2025-01-27 20:44:14 +00:00
loks0n 4320e5bef5 chore: specs 2025-01-27 19:35:09 +00:00
loks0n b070e56226 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into fix-redirect-validator-v2 2025-01-27 19:33:44 +00:00
Eldad A. FuxandGitHub 64567ffd04 Merge pull request #9274 from appwrite/update-specs
Update: Latest sdk specs
2025-01-27 15:40:20 +01:00
Chirag Aggarwal 70c119b9cc chore: added test cases for forwarded user agent 2025-01-27 13:58:46 +00:00
loks0n f369e9100f fix: tests 2025-01-27 13:25:27 +00:00
loks0n c184938a2a fix: tests 2025-01-27 13:02:39 +00:00
Darshan c0dc43fb29 update: specs. 2025-01-27 18:19:07 +05:30
Darshan 5ac95bc103 fix: wrong types on sdk models. 2025-01-27 18:03:31 +05:30
Darshan e9136444d7 update: latest sdk specs. 2025-01-27 17:33:49 +05:30
loks0n dc33c26781 fix: tests 2025-01-27 12:01:18 +00:00
loks0n 3f59c087c8 fix: tests 2025-01-27 11:50:03 +00:00
loks0n d8848e10af fix: build 2025-01-27 11:12:11 +00:00
Luke B. SilverandGitHub c30689d664 Merge branch '1.6.x' into fix-redirect-validator-v2 2025-01-27 11:00:03 +00:00
Luke B. SilverandGitHub bce71d6177 Merge branch '1.6.x' into pla-2009 2025-01-27 10:40:23 +00:00
Eldad A. FuxandGitHub 1db4bf7ae4 Merge pull request #9260 from appwrite/databases-reads-writes
Usage: Databases reads writes
2025-01-27 11:21:52 +01:00
Damodar Lohani 7b588219f9 fix dual writing internal ID conflict 2025-01-27 08:19:03 +00:00
Damodar Lohani 05f99dcdf0 fix wrong arg 2025-01-27 07:46:54 +00:00
Damodar Lohani 3124554403 remove db listeners as counters are now done separately by usage worker 2025-01-27 07:39:33 +00:00
Damodar Lohani 336b0a7c02 rename collection 2025-01-27 07:22:50 +00:00
Damodar Lohani f593498c07 dual writing 2025-01-27 07:15:06 +00:00
Damodar Lohani 55cbab6270 fix format 2025-01-27 06:15:59 +00:00
Damodar Lohani 5d5547c6b9 periodic metric 2025-01-27 06:11:58 +00:00
Damodar Lohani 066273d0db update usage worker for usage count 2025-01-27 04:33:41 +00:00
Damodar Lohani 9955b6b61c usage count container and events 2025-01-27 04:33:33 +00:00
Chirag Aggarwal eea088556d chore: added check for empty forwarded user agent before 2025-01-27 04:15:32 +00:00
Damodar Lohani 5685c8ac25 refactor create collection and create logs db 2025-01-27 02:51:01 +00:00
Damodar Lohani c6360cce06 logs collection config 2025-01-27 02:50:50 +00:00
Damodar Lohani 187ea93f81 Get logs db resource 2025-01-27 02:26:06 +00:00
shimon 7dbb2ce63e Merge branch '1.6.x' of github.com:appwrite/appwrite into 1.6.x 2025-01-26 09:44:21 +02:00
Chirag Aggarwal f74fe4438f chore: override getUserAgent function to set forwarded user agent 2025-01-26 07:11:00 +00:00
Chirag Aggarwal d40fbda04a chore: added teamName to membership redirect url 2025-01-25 17:25:07 +00:00
loks0n b34d64b4d4 fix: tests 2025-01-24 17:58:46 +00:00
loks0n 42cf1fefa5 feat: custom scheme 2025-01-24 16:21:12 +00:00
Christy Jacob 303f7d4845 feat: add fast2SMS adapter 2025-01-24 21:34:00 +05:30
shimon 0e5b59e9f0 payload debug 2025-01-24 09:03:39 +02:00
Christy JacobandGitHub 923dc9661f Merge pull request #9251 from appwrite/feat-enable-heic
feat: enable heic
2025-01-24 10:49:21 +05:30
loks0n 674f3d83b3 chore: remove comment 2025-01-23 17:22:33 +00:00
loks0n 507d210bcb fix: tests 2025-01-23 17:20:57 +00:00
loks0n f5efc53406 fix: tests 2025-01-23 15:44:03 +00:00
loks0n ff9fb9bb46 Merge branch 'fix-redirect-validator-v2' of https://github.com/appwrite/appwrite into fix-redirect-validator-v2 2025-01-23 15:27:11 +00:00
loks0n 49b71ff43a chore: fmt 2025-01-23 15:27:03 +00:00
Luke B. SilverandGitHub d2a4361e8d Merge branch '1.6.x' into fix-redirect-validator-v2 2025-01-23 15:20:52 +00:00
loks0n 170f3656e1 fix: redirect validator v2 2025-01-23 15:00:54 +00:00
Jake BarnbyandGitHub 786c9d1013 Merge pull request #9261 from appwrite/fix-cloud-tests
Fix cloud tests
2025-01-24 03:24:09 +13:00
Jake Barnby 297098b252 Revert test timing changes 2025-01-24 03:23:25 +13:00
Darshan 3cfd592351 fix: tests further. 2025-01-23 19:35:36 +05:30
Darshan 00a2413366 fix: tests due to additional response vars. 2025-01-23 19:17:20 +05:30
Darshan 784cddcf9b Merge branch '1.6.x' into databases-reads-writes 2025-01-23 18:54:28 +05:30
Darshan 08f2a59766 add: database read, write metrics. 2025-01-23 18:54:02 +05:30
Jake BarnbyandGitHub 2af7a46aca Merge pull request #9255 from appwrite/feat-batch-usage-dump
Feat batch usage dump
2025-01-24 02:10:54 +13:00
Jake Barnby f849fb9b30 Add project job timing 2025-01-24 02:04:18 +13:00
Jake Barnby 1360c16f65 Partial revert "Add project log"
This partially reverts commit 8097f1607f.
2025-01-24 01:43:25 +13:00
Jake Barnby 8097f1607f Add project log 2025-01-24 01:34:44 +13:00
Jake Barnby 03a8f35082 Ensure wait overlaps aggregation interval 2025-01-24 00:14:30 +13:00
Jake Barnby 5dd3b09ca7 Lint 2025-01-24 00:13:13 +13:00
Jake Barnby 91c319bc50 Remove redundant logs 2025-01-24 00:09:08 +13:00
Jake Barnby 687be05d95 Speed up dev mode usage tests 2025-01-24 00:08:04 +13:00
Jake Barnby fe930d8f64 Fix tests 2025-01-24 00:07:45 +13:00
Jake Barnby 77ae8dcc63 Lint 2025-01-23 23:28:15 +13:00
Jake Barnby 8cd4000751 One write per project 2025-01-23 23:25:10 +13:00
Chirag Aggarwal 6d404ada74 chore: update scopes 2025-01-23 05:36:12 +00:00
Jake Barnby 65c2b99dfe Fix method refs 2025-01-23 18:32:36 +13:00
Jake Barnby 2a03bbf5b8 Update database 2025-01-23 18:20:00 +13:00
Christy JacobandGitHub 85539dfb29 Merge pull request #9253 from appwrite/fix-amazon-oauth
fix: amazon oauth
2025-01-23 10:35:57 +05:30
Jake Barnby 5e9955ed3e Update database 2025-01-23 17:42:12 +13:00
Jake Barnby b56fda0029 Merge remote-tracking branch 'origin/1.6.x' into feat-batch-usage-dump 2025-01-23 17:42:00 +13:00
Jake BarnbyandGitHub 871043a700 Merge pull request #9256 from appwrite/fix-delete-collection-relations
Delete collection before Appwrite's attributes
2025-01-23 17:25:34 +13:00
fogelito ee9bf984c7 Delete collection first 2025-01-22 17:35:30 +02:00
Jake Barnby 343ef4944b Format 2025-01-23 01:57:23 +13:00
Jake Barnby e0eea5b13d Update db 2025-01-23 01:47:06 +13:00
Jake Barnby d533113d86 Update usage dump to upsert documents in batches 2025-01-23 01:44:33 +13:00
Jake BarnbyandGitHub 758d8496c1 Merge pull request #9237 from appwrite/feat-1.6.x-new-sdk-class
Implement new SDK Class on 1.6.x
2025-01-22 20:38:10 +13:00
Bradley Schofield df491f6f46 Fix null deprecation warning 2025-01-22 16:02:56 +09:00
Bradley Schofield 5123cf5f8e Make Method Extendable 2025-01-22 11:08:23 +09:00
loks0n bc3ac237be fix: add heic input/output 2025-01-21 18:06:20 +00:00
Chirag Aggarwal ead7a17645 chore: remove comma 2025-01-21 13:34:45 +00:00
Chirag Aggarwal f6385c716e fix: encoding state 2025-01-21 12:17:48 +00:00
loks0n 15aa0e0315 feat: enable heic 2025-01-21 11:09:16 +00:00
Jake Barnby 4bb11ef9c2 Lint 2025-01-21 21:40:55 +13:00
Jake Barnby 00419e5a3e Update dev 2025-01-21 21:40:29 +13:00
Jake Barnby 473248b248 Merge remote-tracking branch 'origin/1.6.x' into fix-dead-connections
# Conflicts:
#	composer.json
#	composer.lock
2025-01-21 21:38:55 +13:00
Christy JacobandGitHub c4053b62e4 Merge pull request #9246 from appwrite/fix-phone-validation
fix: phone number parsing exception handling
2025-01-21 12:42:56 +05:30
Christy JacobandGitHub b4e1101a9b Merge pull request #9211 from appwrite/add-user-types
Update audits to include user type
2025-01-21 12:41:23 +05:30
Chirag Aggarwal 27459cc2c0 chore: added test comment 2025-01-21 07:04:16 +00:00
Christy JacobandGitHub 9c71af219b Merge pull request #9250 from appwrite/chore-remove-error
chore: skipped job instead of throwing error
2025-01-21 12:32:36 +05:30
Darshan bc533aeeaa update: change var name. 2025-01-21 12:11:20 +05:30
Darshan 3c3559aee5 update: apply comment suggestion. 2025-01-21 12:07:03 +05:30
Darshan f8f403a66a remove: comments. 2025-01-21 12:02:08 +05:30
Darshan be2929f07f Merge branch '1.6.x' into add-user-types 2025-01-21 11:56:36 +05:30
Chirag Aggarwal af8cb29bfb chore: added log 2025-01-21 06:21:09 +00:00
Christy JacobandGitHub da07ca5391 Merge pull request #9188 from appwrite/feat-update-sdk-gen
Update SDK Generator versions
2025-01-20 19:26:18 +05:30
Chirag Aggarwal 07630c272d chore: update test 2025-01-20 12:41:57 +00:00
Christy JacobandGitHub 6256a09902 Merge pull request #9233 from ChiragAgg5k/fix-benchmark-test
chore: fix benchmarking test when made from fork
2025-01-20 17:27:25 +05:30
Jake BarnbyandGitHub cf8da3495c Merge branch '1.6.x' into feat-1.6.x-new-sdk-class 2025-01-21 00:50:16 +13:00
Bradley Schofield 336dd824f6 Merge branch '1.6.x' into feat-update-sdk-gen
# Conflicts:
#	composer.lock
2025-01-20 19:38:53 +09:00
Jake BarnbyandGitHub 190b9cdaee Merge pull request #9226 from appwrite/index-dependency-exception
Add index dependency exception
2025-01-20 23:24:50 +13:00
Christy JacobandGitHub d8f572925c Merge pull request #9248 from appwrite/fix-maintenance-job
chore: add missing case for executions
2025-01-20 15:15:39 +05:30
Christy Jacob 2533a95b8b chore: add missing case for executions 2025-01-20 15:13:50 +05:30
fogelito 9c95c162d9 bumb database version 2025-01-20 11:28:48 +02:00
fogelito 0915f23e99 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into index-dependency-exception
# Conflicts:
#	composer.lock
2025-01-20 11:27:20 +02:00
Jake BarnbyandGitHub 26036db5f9 Merge pull request #9247 from appwrite/fix-maintenance-job
Fix maintenance job
2025-01-20 22:03:02 +13:00
Christy Jacob c3e2a3fac2 Merge branch '1.6.x' of github.com:appwrite/appwrite into fix-maintenance-job 2025-01-20 14:31:31 +05:30
Christy Jacob b87c750536 chore: add temporary check to deletes worker to prevent it from throwing an exception 2025-01-20 14:31:22 +05:30
Chirag Aggarwal ca0459046d chore: remove isValidNumber check 2025-01-20 08:33:49 +00:00
Chirag Aggarwal 8fb772340f chore: shifted validation 2025-01-20 08:12:42 +00:00
Bradley Schofield f4c1c72bcf Merge branch '1.6.x' into feat-update-sdk-gen
# Conflicts:
#	composer.lock
2025-01-20 16:51:52 +09:00
Jake Barnby ec16ec2ff1 Revert "Use connection for project on create"
This reverts commit 75d3d42de6.
2025-01-20 19:28:34 +13:00
Chirag Aggarwal 7315294555 fix: phone number parsing exception handling 2025-01-20 05:44:19 +00:00
Bradley Schofield e7b119abae Merge branch '1.6.x' into feat-1.6.x-new-sdk-class 2025-01-20 14:21:26 +09:00
Jake Barnby ce7ef42b61 Fix getProjectDB resource 2025-01-20 18:01:07 +13:00
Jake Barnby ef4cc82ded Connection in project for DB 2025-01-20 17:56:51 +13:00
Jake Barnby 099235c69f Revert "Disallow injecting platform DB as project DB"
This reverts commit 09c132414f.

# Conflicts:
#	src/Appwrite/Platform/Workers/Deletes.php
2025-01-20 17:55:48 +13:00
Jake Barnby 62c0226e1b Format 2025-01-20 17:11:40 +13:00
Jake Barnby 4557f2a2df Merge remote-tracking branch 'origin/1.6.x' into fix-dead-connections
# Conflicts:
#	composer.lock
2025-01-20 17:08:55 +13:00
Jake Barnby 09c132414f Disallow injecting platform DB as project DB 2025-01-20 16:57:40 +13:00
Jake BarnbyandGitHub 37fc9fdcd3 Merge pull request #9245 from appwrite/chore-update-fetch
Update Fetch to 0.3.0
2025-01-20 16:19:21 +13:00
Bradley Schofield f3b395f3f3 Add remaining missing descriptions and enable the desc check 2025-01-20 12:16:00 +09:00
Jake Barnby 75d3d42de6 Use connection for project on create 2025-01-20 15:28:50 +13:00
Jake Barnby 7f575acdf6 Remove PDO persist 2025-01-20 15:15:35 +13:00
Bradley SchofieldandGitHub 0408de3253 Update Fetch to 0.3.0 2025-01-20 02:14:29 +00:00
Jake Barnby 5861111cce Merge remote-tracking branch 'origin/1.6.x' into fix-dead-connections 2025-01-20 14:29:18 +13:00
fogelito 028cd935d9 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into index-dependency-exception 2025-01-19 08:30:59 +02:00
fogelito fba5060469 Bumb version 2025-01-19 08:30:02 +02:00
fogelito 733434fdb8 formatOptions default 2025-01-19 08:18:22 +02:00
Steven NguyenandGitHub 54a46d7d26 Merge pull request #9218 from ChiragAgg5k/fix-resend-invitation
fix: resend invitation
2025-01-17 12:59:31 -08:00
Ebenezer DonandGitHub bbb6b2d256 Merge branch '1.6.x' into docs-clarify-updates-endpoints-2 2025-01-17 20:52:46 +00:00
Ebenezer Don 45a03822ee remove addition to gitignore 2025-01-17 20:51:50 +00:00
Chirag Aggarwal be5c4b753a fix: syntax 2025-01-17 19:40:33 +00:00
Chirag AggarwalandGitHub 4d86096c20 Merge branch '1.6.x' into fix-slack-oauth 2025-01-18 00:12:38 +05:30
Christy JacobandGitHub d21d18fe18 Merge pull request #9238 from appwrite/fix-maintenance-job
fix: maintenance job missing type
2025-01-17 23:11:15 +05:30
Chirag Aggarwal 1d1b83afb6 chore: review 2025-01-17 17:28:03 +00:00
Christy Jacob dc651f632a fix: maintenance job missing type 2025-01-17 22:55:16 +05:30
Christy Jacob e7d33ebc52 fix: maintenance job missing type 2025-01-17 22:55:00 +05:30
Christy Jacob e4efaf1efa fix: maintenance job missing type 2025-01-17 22:53:31 +05:30
Chirag Aggarwal 0517ebfb4e fix: auth user 2025-01-17 16:19:14 +00:00
Christy JacobandGitHub b81e15f5d4 Merge pull request #9168 from appwrite/optimise-webhook-events
Optimise webhook events
2025-01-17 19:51:02 +05:30
Christy JacobandGitHub b3fb7cfdb6 Merge branch '1.6.x' into optimise-webhook-events 2025-01-17 18:01:37 +05:30
Christy JacobandGitHub 1b53df4a32 Merge pull request #9232 from appwrite/chore-optimise-events-payloads
feat: optimise events payloads
2025-01-17 17:54:42 +05:30
Christy Jacob 5240d0f9c6 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into optimise-webhook-events 2025-01-17 11:48:41 +00:00
Christy Jacob 5b28e7c478 chore: review comments 2025-01-17 11:48:19 +00:00
Luke B. SilverandGitHub 31ab247b08 Merge branch '1.6.x' into fix-benchmark-test 2025-01-17 11:18:01 +00:00
Christy JacobandGitHub ab54280d79 Merge pull request #9235 from appwrite/fix-funciton-usage
fix: function usage
2025-01-17 15:58:06 +05:30
loks0n 40f16fab50 docs: add compression vars to documentation 2025-01-17 10:17:49 +00:00
loks0n e343a1068e fix: linter 2025-01-17 09:55:14 +00:00
Bradley Schofield 02e7f91e59 Address all missing descriptions apart from ones in projects.php 2025-01-17 16:44:25 +09:00
Chirag Aggarwal 55360c3af0 debugging 2025-01-17 07:43:50 +00:00
Chirag Aggarwal 983eee9266 chore: added error handling to user 2025-01-17 07:14:48 +00:00
Chirag Aggarwal 5d3071e084 fix: scopes 2025-01-17 07:05:31 +00:00
Jake Barnby 99e764cc51 Update lock 2025-01-17 19:21:45 +13:00
Christy Jacob ef0b836987 chore: composer update 2025-01-17 11:19:34 +05:30
Christy Jacob 455bc3370c chore: fix tests 2025-01-17 11:16:21 +05:30
Christy Jacob f66f75b808 chore: review comments 2025-01-17 10:43:02 +05:30
Christy Jacob 458b7bb877 Merge branch '1.6.x' of github.com:appwrite/appwrite into chore-optimise-events-payloads 2025-01-17 10:42:16 +05:30
Christy Jacob a2962b6b95 chore: review comments 2025-01-17 10:42:06 +05:30
Christy Jacob 8bdafb46d6 chore: review comments & refactoring 2025-01-17 10:38:39 +05:30
Bradley Schofield 20e87cb82f Run Linter 2025-01-17 13:39:16 +09:00
Bradley Schofield d5604efef8 Fix actions 2025-01-17 13:38:38 +09:00
Bradley Schofield 95dcb67747 Implement new SDK Class on 1.6.x 2025-01-17 13:31:39 +09:00
Jake Barnby d7c12babf2 Merge remote-tracking branch 'origin/1.6.x' into fix-dead-connections
# Conflicts:
#	composer.json
#	composer.lock
2025-01-17 17:21:30 +13:00
Chirag AggarwalandGitHub c098b62d59 Merge branch '1.6.x' into fix-resend-invitation 2025-01-17 09:28:57 +05:30
Chirag Aggarwal a1031ab921 fix: update membership 2025-01-17 03:58:23 +00:00
Ebenezer Don 5cc8d7ced1 Update specs 2025-01-16 18:21:09 +00:00
Luke B. SilverandGitHub 83a911fb88 Merge branch '1.6.x' into fix-funciton-usage 2025-01-16 18:06:19 +00:00
loks0n facdf1a362 chore: revert usage debug 2025-01-16 18:05:49 +00:00
Chirag AggarwalandGitHub d0760aee20 Merge branch '1.6.x' into fix-benchmark-test 2025-01-16 23:18:45 +05:30
loks0n 7db65f46ae feat: add compression toggle env 2025-01-16 17:23:22 +00:00
loks0n f392b5ab45 chore: composer update 2025-01-16 16:01:35 +00:00
fogelito 8c90973b61 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into index-dependency-exception 2025-01-16 17:34:16 +02:00
fogelito 13687f7fc3 Use 1.6.x 2025-01-16 17:33:51 +02:00
fogelito 1b4a96b1fb Remove code from worker 2025-01-16 17:22:53 +02:00
fogelito 0afbfe2f84 INDEX_DEPENDENCY 2025-01-16 17:20:30 +02:00
Ebenezer Don 4e1494d16d docs: clarify update endpoints only work on draft messages 2025-01-16 13:36:21 +00:00
Christy Jacob f52b2d5dcd chore: review comments 2025-01-16 16:59:30 +05:30
Jake BarnbyandGitHub 2b8772fad2 Merge pull request #9060 from appwrite/feat-apns-content-available
Add new push message parameters
2025-01-16 22:21:46 +13:00
Christy Jacob 2a0894f3d1 fix: incorrect project payloads 2025-01-16 13:29:08 +05:30
Christy Jacob e8f97baf26 fix: incorrect project payloads 2025-01-16 13:14:47 +05:30
Chirag Aggarwal e278b8a5a2 chore: added if check 2025-01-16 07:35:43 +00:00
Chirag Aggarwal 8e474676d3 chore: explicitly added token 2025-01-16 06:36:31 +00:00
Chirag Aggarwal 8dc3b4ca60 fix: tests: 2025-01-16 06:15:41 +00:00
Chirag AggarwalandGitHub 098c1e984e Merge branch '1.6.x' into fix-resend-invitation 2025-01-16 11:37:25 +05:30
Christy Jacob 8ea1f436c1 feat: linter 2025-01-16 11:37:02 +05:30
Christy Jacob de6fced412 feat: optimise events payloads 2025-01-16 11:35:22 +05:30
Chirag Aggarwal 23d20b7d02 fix: invited user's name 2025-01-16 04:29:11 +00:00
Chirag Aggarwal 120ceeedad chore: review 2025-01-16 04:27:25 +00:00
loks0n b16b662a21 debug: log usage 2025-01-15 14:53:38 +00:00
Chirag Aggarwal 779510a605 fix: scopes, and updated to v2 2025-01-15 14:49:26 +00:00
fogelito 2ca690ccbd formatting 2025-01-15 16:29:17 +02:00
fogelito c4bdd2fe1b Change to 409 2025-01-15 16:26:57 +02:00
fogelito 098c30cd9a Message null 2025-01-15 14:02:30 +02:00
fogelito cee3da2ef5 Remove previous 2025-01-15 13:55:18 +02:00
fogelito a3bd26d7f5 Return on Dependency 2025-01-15 13:45:48 +02:00
fogelito 6067fb77b7 Return on Dependency 2025-01-15 13:40:41 +02:00
fogelito 9f82cd78fb Update available 2025-01-15 13:20:08 +02:00
fogelito 43db5b4552 catch Dependency 2025-01-15 13:10:03 +02:00
fogelito a1566b8974 composer.lock 2025-01-15 11:46:07 +02:00
fogelito 719c0bf440 composer.lock 2025-01-15 11:45:26 +02:00
fogelito 353637b9a8 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into index-dependency-exception 2025-01-15 11:17:10 +02:00
fogelito 42ef7fd2d8 INDEX_DEPENDENCY 2025-01-15 11:16:10 +02:00
Jake BarnbyandGitHub 2f83bec071 Merge pull request #9225 from appwrite/fix-empty-related-attribute
Add relatedCollection default
2025-01-15 22:08:23 +13:00
fogelito bbeb9d4719 Add relatedCollection default 2025-01-15 10:13:30 +02:00
Jake Barnby c828ade22c Revert platform update 2025-01-15 18:50:39 +13:00
Jake Barnby c0a0f1016e Revert platform update 2025-01-15 18:36:30 +13:00
Jake Barnby 4e0ad8650a Update deps 2025-01-15 17:58:47 +13:00
Jake Barnby 3f620bbc44 Merge pull request #9213 from appwrite/fix-dead-connections
Fix dead connections
2025-01-15 15:27:42 +13:00
Jake Barnby 7adf81708c Revert "Force CI"
This reverts commit 651c83ee95.
2025-01-15 15:23:57 +13:00
Jake Barnby 651c83ee95 Force CI 2025-01-15 15:23:52 +13:00
Jake Barnby a7f40708a3 Update lock 2025-01-15 15:20:15 +13:00
Jake Barnby 8bb1d23a9b Update pools 2025-01-15 15:11:28 +13:00
Jake Barnby 6e7997a8a8 Merge remote-tracking branch 'origin/1.6.x' into feat-apns-content-available
# Conflicts:
#	composer.lock
2025-01-15 15:01:55 +13:00
Darshan 25431adafb address comment: anonymous > guest. 2025-01-14 17:45:49 +05:30
Chirag Aggarwal d43507040c fix: tests 2025-01-14 10:43:48 +00:00
Chirag AggarwalandGitHub eee9c9d693 Merge branch '1.6.x' into fix-resend-invitation 2025-01-14 16:07:22 +05:30
ChiragAgg5k 860c917cf6 fix: increment 2025-01-14 15:48:28 +05:30
ChiragAgg5k 1c7867ee03 fix: not sending mail to appuser 2025-01-14 15:47:05 +05:30
ChiragAgg5k 2571f7d3fc refactor: createdMembership 2025-01-14 15:43:00 +05:30
Jake BarnbyandGitHub f54684133d Merge pull request #9208 from appwrite/fix-perplexity-ai-template-scopes
Add missing scope on function template
2025-01-14 21:26:31 +13:00
DarshanandGitHub 99a1c19bad Merge branch '1.6.x' into add-user-types 2025-01-14 13:47:15 +05:30
Darshan fabf581d21 fix: tests. 2025-01-14 13:47:01 +05:30
Jake BarnbyandGitHub d9acec6324 Merge pull request #9220 from appwrite/chore-log-populated-path
Log path with populated parameters
2025-01-14 21:08:22 +13:00
DarshanandGitHub 04b2fc246e Merge branch '1.6.x' into add-user-types 2025-01-14 13:19:51 +05:30
Darshan e9479db0fa add: anonymous user type to audit/activity. 2025-01-14 13:18:11 +05:30
Jake Barnby 3fa78e3c3b Log path with populated parameters 2025-01-14 14:50:40 +13:00
Chirag Aggarwal e6cb17a6cc chore: remove TEAM_INVITE_ALREADY_EXISTS 2025-01-13 18:51:23 +00:00
Chirag Aggarwal b5449a8ca6 fix: resend invitation 2025-01-13 18:25:48 +00:00
Eldad A. FuxandGitHub 4ac21afc90 Merge pull request #9153 from ItzNotABug/split-collections
Split the collection definitions
2025-01-13 14:30:26 +01:00
Darshan 993686c4d1 Merge branch '1.6.x' into 'split-collections'. 2025-01-13 18:13:12 +05:30
Eldad A. FuxandGitHub 2ab6ab8cb2 Merge pull request #9163 from ItzNotABug/add-logs-to-db
Add logs db init for consistency
2025-01-13 13:32:44 +01:00
Jake BarnbyandGitHub 32c95391bf Merge pull request #9214 from appwrite/revert-9213-fix-dead-connections
Revert "Fix dead connections"
2025-01-13 19:28:13 +13:00
Jake BarnbyandGitHub 1cd7c397de Revert "Fix dead connections" 2025-01-13 19:24:58 +13:00
DarshanandGitHub 17311bac6d Merge branch '1.6.x' into add-logs-to-db 2025-01-13 11:20:31 +05:30
DarshanandGitHub d997b558dc update: there are no multiple connections for logs.
As confirmed by Damodar, Logs DB is similar to Console DB. so no multiple connections.
2025-01-13 11:20:17 +05:30
Jake BarnbyandGitHub 9e8ed3e206 Merge pull request #9213 from appwrite/fix-dead-connections
Fix dead connections
2025-01-13 18:26:54 +13:00
Christy JacobandJake Barnby cde1d6e83e Merge pull request #9190 from appwrite/fix-dead-connections
Fix dead connections
# Conflicts:
#	composer.lock
2025-01-13 18:03:40 +13:00
Jake BarnbyandGitHub 7db24443ba Merge pull request #9202 from appwrite/chore-update-database
Update database
2025-01-13 16:43:15 +13:00
Jake Barnby 10e036958a Update libraries 2025-01-13 15:28:30 +13:00
Jake Barnby 00a5b8fbb9 Revert "Update database"
This reverts commit a45e0a59dd.

# Conflicts:
#	composer.lock
2025-01-13 14:58:03 +13:00
Jake Barnby c6dd2dbf40 Merge remote-tracking branch 'origin/1.6.x' into chore-update-database 2025-01-13 14:17:11 +13:00
Darshan 377627118f address comments: change type names. 2025-01-12 18:27:58 +05:30
Darshan f4f0b3f0fe add: userTypeto audit data. 2025-01-12 17:06:27 +05:30
Darshan e237f7c05d address comments: make things less complex. 2025-01-12 16:58:38 +05:30
Darshan 1e62e26ef1 add: userType and keyName to Audits. 2025-01-12 12:42:52 +05:30
Steven NguyenandGitHub 86874cbec1 Merge pull request #9209 from appwrite/fix-9052-delete-session
Ensure user can delete session
2025-01-11 11:09:20 -08:00
Steven NguyenandGitHub f48c843bea fix(users): ensure user can delete session
The session document created by users.createSession() was missing
delete permissions for the user so when the user tried to delete it,
they got a 401 error.

This PR ensure the permissions are added just like if the document was
created from the Account API so that the user has access to delete the
document.
2025-01-11 17:56:36 +00:00
Darshan 4fe2611ccc add: missing scope to template to prevent a crash on console. 2025-01-11 11:07:10 +05:30
Christy JacobandGitHub 9b404db87f Merge pull request #9207 from appwrite/fix-abuse-sms
fix sms usage
2025-01-10 23:07:07 +05:30
loks0n 1695e90399 fix: metric 2025-01-10 17:33:08 +00:00
Steven NguyenandGitHub c2124d6803 Merge pull request #9200 from ChiragAgg5k/fix-email-otp
fix: disabling of email-otp when user wants to
2025-01-10 07:23:20 -08:00
Christy JacobandGitHub 5dc04ca8ca Merge pull request #9056 from appwrite/PLA-1958
chore: add audit labels to project resources
2025-01-10 17:57:15 +05:30
Fabian GruberandGitHub 26c86d465b Merge branch '1.6.x' into PLA-1958 2025-01-10 13:18:53 +01:00
Christy JacobandGitHub f153e1bebf Merge pull request #9138 from appwrite/fix-installation-oauth-collision
Fix: VCS identity collision
2025-01-10 17:08:31 +05:30
Christy JacobandGitHub a7fdfc629c Merge pull request #9198 from appwrite/feat-project-sms-usage
feat project sms usage
2025-01-10 15:42:00 +05:30
Jake Barnby 428efdede0 Update lock 2025-01-10 22:32:25 +13:00
Christy JacobandGitHub 619b716233 Merge pull request #9192 from ItzNotABug/fix-messaging-test-for-audits
Add assertEventually to messaging provider logs test
2025-01-10 14:50:31 +05:30
Christy JacobandGitHub 595e93e59e Merge branch '1.6.x' into fix-messaging-test-for-audits 2025-01-10 14:50:23 +05:30
Jake Barnby 9e5189fa09 Merge remote-tracking branch 'origin/1.6.x' into chore-update-database
# Conflicts:
#	composer.lock
2025-01-10 22:14:06 +13:00
Chirag AggarwalandGitHub 642eac098c Merge branch '1.6.x' into fix-email-otp 2025-01-10 14:40:28 +05:30
Jake Barnby a45e0a59dd Update database 2025-01-10 22:05:40 +13:00
Jake BarnbyandGitHub 6ebab2f346 Merge pull request #9201 from appwrite/revert-9190-fix-dead-connections
Revert "Fix dead connections"
2025-01-10 20:36:36 +13:00
Jake Barnby b76260ecce Update database 2025-01-10 20:28:50 +13:00
Jake BarnbyandGitHub b88d9c51fb Revert "Fix dead connections" 2025-01-10 20:19:34 +13:00
DarshanandGitHub 4bfd132eee Merge branch '1.6.x' into fix-messaging-test-for-audits 2025-01-10 10:26:30 +05:30
ChiragAgg5k d86984f6cf chore: used config in auth 2025-01-10 08:42:10 +05:30
loks0n ec7f76f9fb fix: stats tests 2025-01-09 21:17:53 +00:00
loks0n a85abdab4a fix: usage tests 2025-01-09 20:11:02 +00:00
loks0n cc7cef59b3 isset over empty 2025-01-09 19:03:25 +00:00
loks0n ee9e4f90c1 chore: specs 2025-01-09 18:57:00 +00:00
loks0n 9687fdb0a5 Merge branch 'feat-project-sms-usage' of https://github.com/appwrite/appwrite into feat-project-sms-usage 2025-01-09 18:33:08 +00:00
loks0n 7c9c3d9214 feat: sms rates 2025-01-09 18:33:07 +00:00
Luke B. SilverandGitHub 7847dfe9c0 Merge branch '1.6.x' into feat-project-sms-usage 2025-01-09 17:20:52 +00:00
loks0n 76e1b27cef fix: create dummy resource 2025-01-09 16:41:24 +00:00
loks0n 651a3d1d62 test: fix 2025-01-09 16:37:26 +00:00
loks0n d068067a98 feat: project sms usage 2025-01-09 16:35:40 +00:00
Darshan bce68b2202 fix: benchmark test. 2025-01-09 19:45:03 +05:30
Christy JacobandGitHub 4a196562c5 Merge pull request #9191 from ChiragAgg5k/feat-free-sms-limit-authphone 2025-01-08 22:11:34 +05:30
Luke B. SilverandGitHub 16b32a1069 Merge branch '1.6.x' into feat-free-sms-limit-authphone 2025-01-08 14:07:40 +00:00
Christy JacobandGitHub 16b90d723c Merge pull request #9165 from ItzNotABug/add-hostname-to-audits
Add hostname to audits
2025-01-08 19:34:08 +05:30
DarshanandGitHub 2ea3bad502 Merge branch '1.6.x' into add-hostname-to-audits 2025-01-08 19:11:23 +05:30
Darshan 0daf4057f3 fix: benchmark test. 2025-01-08 19:10:30 +05:30
Darshan a82a0a2f15 Merge remote-tracking branch 'origin/fix-messaging-test-for-audits' into fix-messaging-test-for-audits 2025-01-08 18:57:31 +05:30
DarshanandGitHub 11f3c60554 Merge branch '1.6.x' into fix-messaging-test-for-audits 2025-01-08 18:50:13 +05:30
Darshan e3637e4ba5 update: lets clear this test too, it has been a while now. 2025-01-08 18:40:00 +05:30
Matej BačoandGitHub 78c6766baf Merge branch '1.6.x' into fix-installation-oauth-collision 2025-01-08 14:06:45 +01:00
Matej Bačo 4226e92387 Improve DB schema 2025-01-08 13:05:49 +00:00
Darshan addb56ab7d update: shift comment. 2025-01-08 18:24:53 +05:30
Darshan 88755e6d38 update: add sleep to tests. 2025-01-08 18:21:35 +05:30
ChiragAgg5k 2178bb44cd fix: attribute 2025-01-08 17:14:07 +05:30
ChiragAgg5k 1f240ca27a chore: moved non-countrycode tracking outside abuse 2025-01-08 17:09:55 +05:30
ChiragAgg5k ff056fa145 chore: shifted authphone usage tracking to api calls 2025-01-08 17:01:01 +05:30
Christy JacobandGitHub 910bd4d8ea Merge pull request #9190 from appwrite/fix-dead-connections
Fix dead connections
2025-01-08 15:00:15 +05:30
Jake Barnby 941c9f1905 Add connection health check to http error handler 2025-01-08 21:15:33 +13:00
Jake Barnby 7f6a43cf7b Update to release 2025-01-08 21:00:02 +13:00
Jake Barnby 39f34135f0 Update pools 2025-01-08 20:43:45 +13:00
Bradley SchofieldandGitHub 02ab3c850e Update SDK Generator versions 2025-01-08 06:40:33 +00:00
Jake Barnby 38ee612164 Update pools 2025-01-08 18:39:26 +13:00
Jake Barnby 71b892132b Mark connections as unhealthy on lost connection error from DB 2025-01-08 18:39:06 +13:00
Jake Barnby 2c04ace022 Add connection for project 2025-01-08 18:38:24 +13:00
Jake BarnbyandGitHub f4af241804 Merge pull request #9157 from appwrite/delete-attribute-not-found
Delete attribute No throwing Exception on not found
2025-01-08 16:07:53 +13:00
Jake BarnbyandGitHub 077313fe2d Update src/Appwrite/Platform/Workers/Databases.php 2025-01-08 15:46:35 +13:00
Christy JacobandGitHub b64eb2df38 Merge pull request #9146 from appwrite/feat-rollback-specifications
Set base specification CPUs to 0.5 again
2025-01-07 12:34:23 +05:30
Eldad A. FuxandGitHub 3b9cbcb671 Merge pull request #9142 from appwrite/usage-db-read-writes-addition
Usage databases api read writes addition
2025-01-06 19:38:41 +01:00
shimon f2cbdbae87 added operations debug header 2025-01-06 19:32:19 +02:00
shimon 347ba4a127 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into usage-db-read-writes-addition 2025-01-06 19:30:20 +02:00
shimon 5c097df56a added operations debug header 2025-01-06 19:07:18 +02:00
shimon c97b48f443 Merge remote-tracking branch 'origin/usage-db-read-writes-addition' into usage-db-read-writes-addition
# Conflicts:
#	app/controllers/api/databases.php
2025-01-06 19:03:32 +02:00
shimon a481e958e3 added operations debug header 2025-01-06 18:57:35 +02:00
fogelito 40b407027e Count changes 2025-01-06 18:51:39 +02:00
shimon b910d795f2 Merge remote-tracking branch 'origin/usage-db-read-writes-addition' into usage-db-read-writes-addition 2025-01-06 18:06:29 +02:00
shimon d64ea97ee6 added operations debug header 2025-01-06 18:06:18 +02:00
fogelito fc03075ef8 Update counts 2025-01-06 18:01:44 +02:00
shimon f70222d41a added operations debug header 2025-01-06 17:19:39 +02:00
shimon 1cfcd53ae3 Merge branch 'usage-db-read-writes-addition' of https://github.com/appwrite/appwrite into usage-db-read-writes-addition 2025-01-06 16:50:54 +02:00
shimon 0814fe49e2 added operations debug header 2025-01-06 16:50:23 +02:00
Eldad A. FuxandGitHub eb8ca0229a Merge pull request #9177 from appwrite/PLA-2158
feat(swoole): allow configuration override of available cpus
2025-01-06 14:18:15 +01:00
Fabian Gruber 1bc419b6a1 feat(swoole): allow configuration override of available cpus 2025-01-06 13:48:01 +01:00
fogelito 895e79e736 Operations 2025-01-06 12:25:56 +02:00
shimon 1a73847c47 added operations debug header 2025-01-06 11:28:26 +02:00
Christy Jacob bdb6599087 chore: linter 2025-01-04 12:55:20 +05:30
Christy Jacob 56af9d0991 chore: add todo 2025-01-04 12:51:50 +05:30
Christy Jacob e1c08e33d4 fix: trigger webhooks events only if a project has webhooks 2025-01-04 12:51:42 +05:30
Christy JacobandGitHub b680c5021a Merge pull request #9155 from appwrite/christyjacob4-patch-1
Update general.php
2025-01-04 12:21:17 +05:30
Darshan 7910d5652d fix: collection creation and tests. 2025-01-03 17:20:27 +05:30
Darshan b7651e4f5e address comment: add files/buckets to common. 2025-01-03 15:56:36 +05:30
Darshan 31f8950b45 add: hostname to audits. 2025-01-03 15:30:55 +05:30
Darshan 0203b67b28 add: logs init. 2025-01-03 10:54:56 +05:30
fogelito e8e39dbd56 Revert lock 2025-01-02 17:57:46 +02:00
fogelito bd083ac280 database 0.53.29 2025-01-02 17:54:47 +02:00
fogelito 06a5876f35 No throwing Exception 2025-01-02 17:06:22 +02:00
shimon 4acc2736e4 added operations debug header 2025-01-02 16:46:38 +02:00
shimon c5a49cc537 added operations debug header 2025-01-02 16:45:21 +02:00
Darshan e895fd6e0c address comment, change var and file name. 2025-01-02 19:19:39 +05:30
Christy JacobandGitHub bdb93cd323 Update general.php 2025-01-02 19:03:28 +05:30
shimon 6b45293f78 file transformations usage 2025-01-02 13:55:30 +02:00
shimon 63cf093020 file transformations usage 2025-01-02 13:53:57 +02:00
shimon 504b7276cb composer 2025-01-02 11:02:53 +02:00
shimon e7e095907c composer 2025-01-02 10:44:56 +02:00
shimon c0d48efe18 composer 2025-01-02 10:29:55 +02:00
shimon fbbc9312ea database crud usage addition 2025-01-02 10:09:44 +02:00
shimon 4cc963d296 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into usage-db-read-writes-addition 2025-01-02 10:00:59 +02:00
shimon c7841bb36e database crud usage addition 2025-01-02 09:59:59 +02:00
shimon 42589de95e database crud usage addition 2025-01-02 09:56:14 +02:00
shimon 0f2985cec1 database crud usage addition 2025-01-02 09:19:09 +02:00
Darshan c7b2a90a0c feat: split collections. 2025-01-02 10:18:47 +05:30
Steven NguyenandGitHub 4ce919c8c6 chore: update composer.lock after merge 2024-12-31 23:11:00 +00:00
Steven NguyenandGitHub a502b1c566 Merge branch '1.6.x' into feat-apns-content-available 2024-12-31 22:56:15 +00:00
Christy JacobandGitHub a08c71d9d2 Merge pull request #9112 from appwrite/chore-release-client-sdks
chore: release client sdks
2024-12-31 11:43:59 +05:30
Bradley Schofield d3b764a9e2 Set base specification CPUs to 0.5 again 2024-12-30 15:10:27 +00:00
Matej BačoandGitHub 98989d77a2 Merge branch '1.6.x' into fix-installation-oauth-collision 2024-12-30 15:06:32 +01:00
Matej Bačo ba04a7760b Backwards-compatibility 2024-12-30 14:43:28 +01:00
loks0n 8c75203298 chore: bump sdk generator 2024-12-30 11:59:19 +00:00
loks0n dd19936fa5 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into chore-release-client-sdks 2024-12-30 11:55:52 +00:00
loks0n dd0658de19 chore: bump versions 2024-12-30 11:26:11 +00:00
Eldad A. FuxandGitHub 0ab86f4dac Merge pull request #9144 from appwrite/feat-remove-firebase-oauth
Remove firebase OAuth API endpoints
2024-12-30 11:06:25 +01:00
Bradley Schofield 26bac5a2ea Remove firebase OAuth API endpoints 2024-12-30 09:00:52 +00:00
shimon dd13e8b3cf database crud usage addition 2024-12-29 18:02:19 +02:00
shimon 8e17fc69b3 database crud usage addition 2024-12-29 17:57:29 +02:00
shimon 025c9aeeb2 database crud usage addition 2024-12-29 17:53:45 +02:00
shimon c23af8168b database crud usage addition 2024-12-29 17:48:05 +02:00
shimon 58fd0d35bd database crud usage addition 2024-12-29 17:47:52 +02:00
Eldad A. FuxandGitHub daad365667 Merge pull request #9139 from appwrite/fix-remove-abuse-delete-trigger
fix: remove abuse delete trigger
2024-12-27 22:52:52 +01:00
loks0n 4691407a7a chore: bump utopia-php/abuse to 0.46.0 2024-12-27 20:41:30 +00:00
loks0n 769772a0a8 fix: remove abuse delete trigger 2024-12-27 20:30:03 +00:00
Eldad A. FuxandGitHub 8025082464 Merge pull request #9137 from appwrite/chore-remove-abuse-cleanup
chore remove abuse cleanup
2024-12-27 18:27:37 +01:00
Matej Bačo fcb517e679 Fix type error 2024-12-27 13:32:22 +01:00
Matej Bačo 297826a62a Fix git identity collisions 2024-12-27 13:13:14 +01:00
loks0n 53860d2b96 chore: remove abuse cleanup 2024-12-27 10:07:53 +00:00
Eldad A. FuxandGitHub 84e5a5f6cb Merge pull request #9125 from appwrite/webhooks-usage-addition
Added webhooks usage stats
2024-12-24 15:18:42 +01:00
shimon 452a58de94 Reset matrics after enqueueing job via usage event 2024-12-24 15:38:01 +02:00
shimon dae5bf593c add webhooks usage stats 2024-12-24 11:09:41 +02:00
shimon 4b033d8f00 add webhooks usage stats 2024-12-24 10:50:09 +02:00
shimon fe19b0205a add webhooks usage stats 2024-12-23 19:44:10 +02:00
Christy JacobandGitHub 85cbc17996 Merge pull request #9124 from appwrite/migrate-to-redis-abuse
Migrate to redis abuse
2024-12-23 02:29:35 +05:30
Christy Jacob cddace70a4 fix: deletes worker abuse retention type 2024-12-23 01:45:57 +05:30
Christy Jacob 1935c7371a Merge branch '1.6.x' of github.com:appwrite/appwrite into migrate-to-redis-abuse 2024-12-23 01:30:28 +05:30
Christy Jacob 25953732ab fix: deletes worker abuse retention type 2024-12-23 01:30:20 +05:30
Christy JacobandGitHub ef032a7af0 Merge pull request #9121 from appwrite/migrate-to-redis-abuse
chore: use redis adapter for abuse
2024-12-20 22:26:28 +05:30
Christy Jacob e9c8766a23 chore: update abuse library 2024-12-20 21:53:12 +05:30
Christy Jacob 50601bd454 chore: use redis adapter for abuse 2024-12-20 20:14:50 +05:30
Christy JacobandGitHub 0715a8450a Merge pull request #9118 from appwrite/chore-optimise-webhooks-payload
chore: fix attribute name
2024-12-19 21:42:13 +05:30
Christy Jacob e2bdb46d48 chore: fix attribute name 2024-12-19 21:17:27 +05:30
Christy Jacob 718edd37f7 chore: fix attribute name 2024-12-19 21:15:13 +05:30
Christy JacobandGitHub ea4d41d929 Merge pull request #9117 from appwrite/revert-9111-migrate-to-redis-abuse
Revert "chore: initial commit"
2024-12-19 20:28:09 +05:30
Christy JacobandGitHub b97dea7996 Revert "chore: initial commit" 2024-12-19 20:27:54 +05:30
Christy JacobandGitHub 610121d02c Merge pull request #9115 from appwrite/chore-optimise-webhooks-payload
chore: optimise webhooks payload
2024-12-19 17:33:24 +05:30
Christy Jacob 6ec640cfd5 chore: linter 2024-12-19 16:56:17 +05:30
Christy Jacob d03d3c05b4 chore: optimise webhooks payload 2024-12-19 16:52:10 +05:30
Christy Jacob 53d8e2b0c3 chore: optimise webhooks payload 2024-12-19 16:43:05 +05:30
Christy JacobandGitHub 153431246d Merge pull request #9111 from appwrite/migrate-to-redis-abuse
chore: initial commit
2024-12-18 20:59:40 +05:30
Christy Jacob 12cb3b10bd chore: update abuse library 2024-12-18 20:42:29 +05:30
Christy Jacob 5a9dbe9c80 chore: fix realtime test 2024-12-18 20:06:24 +05:30
Christy Jacob d0f04126f3 chore: add missing resources to workers 2024-12-18 19:59:49 +05:30
Christy Jacob 599cc5e19a chore: review comments 2024-12-18 15:57:04 +05:30
Luke B. SilverandGitHub 0e56743dca Merge branch '1.6.x' into chore-release-client-sdks 2024-12-18 10:10:18 +00:00
Christy Jacob 77a8c27153 chore: fix realtime tests 2024-12-18 13:26:31 +05:30
Christy Jacob 911edd9457 chore: linter 2024-12-18 13:08:17 +05:30
Christy Jacob abbb2b544e Merge branch 'migrate-to-redis-abuse' of github.com:appwrite/appwrite into migrate-to-redis-abuse 2024-12-18 12:58:12 +05:30
Christy Jacob f1a089f8c6 feat: fix tests 2024-12-18 12:57:52 +05:30
Christy JacobandGitHub e8a3b87f68 Merge branch '1.6.x' into migrate-to-redis-abuse 2024-12-18 12:46:09 +05:30
Christy Jacob 01393faf0b feat: update abuse library 2024-12-18 11:58:34 +05:30
Christy JacobandGitHub a42fa741b7 Merge pull request #9109 from appwrite/fix-project-accessed-at
Fix: update project accessed at from router and schedulers
2024-12-18 09:56:19 +05:30
Damodar Lohani e24cce4e48 refactor 2024-12-17 14:19:26 +00:00
Damodar Lohani d7d69b63a8 reset worker 2024-12-17 14:15:27 +00:00
Damodar Lohani 2fb9ebe71d update project accessed at in schedules 2024-12-17 14:05:49 +00:00
Damodar Lohani 0cdb2a98be fix db name 2024-12-17 14:05:37 +00:00
Damodar Lohani c7713f58dd remove worker hook 2024-12-17 13:52:58 +00:00
loks0n 47785d9c4d docs: update 2024-12-17 11:58:25 +00:00
loks0n 6372cf59cc chore: composer 2024-12-17 11:54:50 +00:00
Fabian GruberandGitHub 4c7c4d7c7c Merge branch '1.6.x' into PLA-1958 2024-12-17 11:04:20 +01:00
Christy Jacob 52ac358f53 chore: rename variable 2024-12-17 10:23:52 +05:30
Christy Jacob cc867e2859 chore: rename variable 2024-12-17 10:23:09 +05:30
Christy Jacob b55a01c5b3 chore: linter 2024-12-17 10:12:19 +05:30
Christy Jacob ed918e1748 Merge branch '1.6.x' of github.com:appwrite/appwrite into migrate-to-redis-abuse 2024-12-17 10:09:00 +05:30
Christy Jacob 801f311faf feat: cleanup 2024-12-17 10:08:16 +05:30
Christy Jacob 67996a8a14 feat: cleanup 2024-12-17 10:00:45 +05:30
Christy Jacob e8cea70346 feat: cleanup 2024-12-17 10:00:28 +05:30
Damodar Lohani 06c09503df update project rsource 2024-12-17 04:27:28 +00:00
Christy Jacob ee05f3d3bc feat: migrate to redis abuse 2024-12-17 09:49:31 +05:30
Damodar LohaniandGitHub f785602a4d Merge branch '1.6.x' into fix-project-accessed-at 2024-12-17 10:02:20 +05:45
Christy Jacob 396faa56c2 chore: initial commit 2024-12-17 08:21:38 +05:30
Christy JacobandGitHub 8759330896 Merge pull request #9100 from appwrite/feat-upgrade-assistant
feat: upgrade assistant
2024-12-16 19:30:13 +05:30
loks0n a24dc79036 chore: fix endpoint 2024-12-16 12:36:02 +00:00
Damodar Lohani ab4a444154 Fix: project accessed at on workers 2024-12-16 06:40:53 +00:00
Damodar Lohani 515b5904b8 update project accessed at from router 2024-12-16 06:39:25 +00:00
Fabian Gruber 830ad99111 chore: add audit labels to project resources 2024-12-13 14:06:08 +01:00
Luke B. SilverandGitHub ac03a72610 Merge branch '1.6.x' into feat-upgrade-assistant 2024-12-13 10:17:34 +00:00
Christy JacobandGitHub 9c2b78e612 Merge pull request #9102 from appwrite/feat-create-function-abuse-fix
Feat: createFunction abuse labels
2024-12-13 01:10:34 +04:00
Matej Bačo 905b4938a4 Fix failing test 2024-12-12 20:55:33 +00:00
Matej Bačo 8b3f09a981 Env var for function abuse limit 2024-12-12 21:10:22 +01:00
Christy JacobandGitHub a148b624b0 Merge pull request #9104 from appwrite/fix-usage-payload-bug
Usage payload debug
2024-12-13 00:03:06 +04:00
Matej Bačo fcd991fff2 Merge branch 'feat-create-function-abuse-fix' of github.com:appwrite/appwrite into feat-create-function-abuse-fix 2024-12-12 21:00:19 +01:00
Matej Bačo ab7ea056e6 Increase limit 2024-12-12 21:00:04 +01:00
shimon 434aa6d140 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into fix-usage-payload-bug 2024-12-12 21:58:33 +02:00
shimon 01019f5055 Usage payload debug 2024-12-12 21:54:59 +02:00
Matej BačoandGitHub 5a0369ce27 Merge branch '1.6.x' into feat-create-function-abuse-fix 2024-12-12 20:52:39 +01:00
Matej Bačo 04139d6901 Linter fix 2024-12-12 20:52:31 +01:00
Matej Bačo 9c4d0f2a79 Leftover 2024-12-12 20:48:28 +01:00
Matej Bačo a18472507b Add createFunction abuse check + fix abuse 2024-12-12 20:45:49 +01:00
Christy JacobandGitHub 06adb3bcab Merge pull request #9103 from appwrite/fix-usage-payload-bug
Usage payload debug
2024-12-12 23:31:58 +04:00
shimon 177fd92094 Usage payload debug 2024-12-12 21:20:55 +02:00
loks0n bc38fd2640 fix: hide system prompt 2024-12-12 19:04:31 +00:00
loks0n eb2c3cd73f chore: platform bump 2024-12-12 18:55:38 +00:00
Christy JacobandGitHub 796b06fec5 Merge pull request #9101 from appwrite/fix-usage-payload-bug
Usage payload debug
2024-12-12 22:48:50 +04:00
Matej Bačo 6a9eeeb370 Add createFunction abuse labels 2024-12-12 19:39:24 +01:00
shimon 717b06950e Usage payload debug 2024-12-12 20:20:47 +02:00
loks0n a12b758658 feat: upgrade assistant to 0.6,0 2024-12-12 17:11:22 +00:00
Christy JacobandGitHub ef150ac2c0 Merge pull request #9099 from appwrite/fix-usage-payload-bug
Fix usage payload bug
2024-12-12 20:58:12 +04:00
shimon 158954df0b Usage payload debug 2024-12-12 18:55:45 +02:00
shimon c5605ac8be payload bug fix 2024-12-12 17:46:27 +02:00
shimon dc64fe85e9 payload bug fix 2024-12-12 17:46:04 +02:00
Christy JacobandGitHub 653db9d7ad Merge pull request #9098 from appwrite/fix-usage-payload-bug
payload bug fix
2024-12-12 18:57:55 +04:00
shimon 6a28d4eee6 payload bug fix 2024-12-12 16:55:14 +02:00
shimon f6157dd2c0 payload bug fix 2024-12-12 16:44:41 +02:00
Christy JacobandGitHub 9a693ecfb5 Merge pull request #9055 from appwrite/PLA-1990
fix(realtime): decrement connectionCounter only if connection is known
2024-12-12 18:06:32 +04:00
Christy JacobandGitHub 3949ce5f6b Merge pull request #9096 from appwrite/refactor-db-for-console
chore: replace occurrences of dbForConsole to dbForPlatform
2024-12-12 15:15:19 +04:00
Christy JacobandGitHub c2371e14f2 Merge pull request #9097 from appwrite/fix-usage-payload-bug
Fix usage payload bug
2024-12-12 14:37:10 +04:00
shimon ed66e8e41b payload bug fix 2024-12-12 12:36:34 +02:00
shimon 6dcd679412 payload bug fix 2024-12-12 12:34:39 +02:00
shimon b145477114 payload bug fix 2024-12-12 12:30:36 +02:00
Christy Jacob 75b42e134a chore: replace occurrences of dbForConsole to dbForPlatform 2024-12-12 14:30:26 +04:00
Jake BarnbyandGitHub 0c3ed7f81b Merge pull request #9092 from appwrite/migration-throw-error
Migration throw error
2024-12-12 20:01:30 +13:00
Christy JacobandGitHub 3b93b33dc9 Merge pull request #9076 from appwrite/fix-dont-checkout-git-history
feat: delete git folder
2024-12-11 19:11:04 +04:00
fogelito 17b5b3cf4b Remove comments 2024-12-11 17:06:19 +02:00
fogelito 4984abb0dd tests nulls 2024-12-11 16:58:00 +02:00
fogelito 66c5f959c0 Sentry Console::error 2024-12-11 16:34:13 +02:00
fogelito 142782825d Add throwable migration error 2024-12-11 13:31:17 +02:00
fogelito 071f050aaf Add throwable migration error 2024-12-11 12:57:25 +02:00
Jake Barnby 8531b70089 Update docs 2024-12-11 19:15:29 +13:00
Jake Barnby bb6da36b36 Update versions 2024-12-11 19:15:10 +13:00
Jake Barnby d80731e7db Allow pushing SDK to new branches 2024-12-11 19:14:10 +13:00
loks0n 13157837ec Merge branch '1.6.x' of https://github.com/appwrite/appwrite into fix-dont-checkout-git-history 2024-12-10 13:27:54 +00:00
Christy JacobandGitHub 203e11053f Merge pull request #9085 from appwrite/update-usage-payload
Update usage-dump payload
2024-12-10 07:22:29 -06:00
shimon 834c237cd5 handling map size 2024-12-10 15:18:21 +02:00
loks0n f2027bad2c chore: bump composer 2024-12-10 13:12:20 +00:00
Jake Barnby 4a49629a34 Map enum 2024-12-11 00:26:58 +13:00
Jake Barnby 478e19f52f Map push priority enum name 2024-12-11 00:26:01 +13:00
Jake Barnby 636dc8fe9f Update specs 2024-12-10 23:53:56 +13:00
Jake Barnby 6c83eba693 Fix badge default to allow sending clear badge notifications 2024-12-10 23:34:24 +13:00
Jake Barnby 30fed93e2a Remove variable variable usage 2024-12-10 23:33:49 +13:00
Jake Barnby f2b202aa52 Update push message builder 2024-12-10 22:53:40 +13:00
Jake Barnby 15855e2406 Fix message search builder subquery 2024-12-10 22:53:03 +13:00
Jake Barnby 8649032b73 Fix badge param type 2024-12-10 22:52:39 +13:00
shimon 5ffb81df43 update usage project attr 2024-12-09 17:42:05 +02:00
Jake Barnby 44d3a4f223 Update specs 2024-12-09 15:42:20 +13:00
loks0n 2c87e3f4e5 feat: delete git folder 2024-12-06 13:51:24 +00:00
Jake Barnby 409c8a4d8e Merge branch 'feat-apns-content-available' of github.com:appwrite/appwrite into feat-apns-content-available 2024-12-05 23:12:36 +13:00
Jake Barnby 45f6d9c0b9 Specs 2024-12-05 23:12:06 +13:00
Jake BarnbyandGitHub b36d4c2e2f Merge branch '1.6.x' into feat-apns-content-available 2024-12-05 22:18:59 +13:00
Jake Barnby 5b75fcc398 Update lock 2024-12-05 21:39:43 +13:00
526f54cf06 Update src/Appwrite/Platform/Workers/Messaging.php
Co-authored-by: Damodar Lohani <lohanidamodar@users.noreply.github.com>
2024-12-05 21:09:52 +13:00
Jake BarnbyandGitHub dfb5ff1feb Merge pull request #9066 from appwrite/chore-upgrade-utopia-storage
Chore: upgrade utopia storage
2024-12-05 21:00:07 +13:00
Jake Barnby 98149f56b5 Default empty title and body 2024-12-05 17:20:20 +13:00
Jake Barnby f4917f7bc6 Reorder params 2024-12-05 17:04:11 +13:00
Damodar Lohani 968b16d8bf upgrade utopia storage 2024-12-04 08:38:26 +00:00
Jake Barnby 2934bdbd3b Update messaging 2024-12-04 20:16:13 +13:00
Jake Barnby a461198547 Add critical and priority params 2024-12-04 19:27:55 +13:00
Jake Barnby fa49244b4b Fix param type 2024-12-03 22:50:40 +13:00
Jake Barnby ab900d16d4 Allow apns content available 2024-12-03 17:19:06 +13:00
Fabian Gruber 6e4b365992 fix(realtime): decrement connectionCounter only if connection is known 2024-12-02 10:39:38 +01:00
Jake BarnbyandGitHub 3ce63b3d7d Merge pull request #9038 from appwrite/1.6.x-fix-delete-attribute
Fix delete notFound attribute
2024-12-02 13:49:42 +13:00
Steven NguyenandGitHub 3954560e30 Merge pull request #9050 from appwrite/1.6.x-db-0.53.20.a
Bomb db 0.53.200
2024-12-01 00:43:27 -08:00
fogelito 8349a7e8f7 lock 2024-12-01 10:17:28 +02:00
fogelito b923893699 0.53.20.hotfix1 2024-12-01 10:11:19 +02:00
Christy JacobandGitHub 16b5a8507e Merge pull request #9037 from appwrite/lohanidamodar-patch-4
Update storage.php
2024-11-28 12:10:41 +01:00
Christy JacobandGitHub 992808f640 Merge pull request #9039 from appwrite/fix-rules-collection
feat: use environment variable to check rules format
2024-11-28 11:49:50 +01:00
Christy Jacob 4dbf11e236 feat: review comments and linter 2024-11-28 14:33:02 +04:00
Christy Jacob 48d20b2c67 feat: update proxy and certificates files 2024-11-28 14:18:37 +04:00
Christy Jacob 7e98493324 feat: use environment variable to check rules format 2024-11-28 14:08:26 +04:00
fogelito 1054f877a1 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into 1.6.x-fix-delete-attribute 2024-11-28 12:07:40 +02:00
Damodar LohaniandGitHub a4ff1bf3be Update storage.php 2024-11-28 15:47:09 +05:45
fogelito 6be15eee0c try catch not found 2024-11-28 11:59:43 +02:00
Jake BarnbyandGitHub 3d44757e37 Merge pull request #9032 from appwrite/1.6.x-fix-delete-attribute
Remove failed attribute
2024-11-28 16:32:32 +13:00
Jake Barnby 856be8ce0f Merge remote-tracking branch 'origin/1.6.x' into 1.6.x-fix-delete-attribute 2024-11-28 16:14:23 +13:00
Steven NguyenandGitHub 91be70babf Merge pull request #9034 from appwrite/chore-bump-console-5.2.27
Bump console to version 5.2.27
2024-11-27 09:48:39 -08:00
Steven NguyenandGitHub c062a8f129 Bump console to version 5.2.27 2024-11-27 16:47:32 +00:00
Steven NguyenandGitHub 35005657b9 Merge pull request #9026 from appwrite/feat-1.6.1-release-prep
Prepare 1.6.1 release
2024-11-27 08:44:36 -08:00
fogelito 98864b04e1 fix $options 2024-11-27 18:02:37 +02:00
fogelito e39c96e6d1 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into 1.6.x-fix-delete-attribute 2024-11-27 14:37:36 +02:00
fogelito ca6cd349fb Remove failed attribute 2024-11-27 14:35:58 +02:00
Christy JacobandGitHub 441e56f445 Merge pull request #8932 from appwrite/fix-preview-domains-development
Fix: Preview domains locally
2024-11-27 13:13:33 +01:00
Christy JacobandGitHub 92faa24d38 Merge pull request #9028 from appwrite/PLA-1913
feat: add more tags to logging
2024-11-26 16:43:46 +01:00
Christy JacobandGitHub 399261bfb1 Merge pull request #9029 from appwrite/1.6.x-fix-restoration
fix restoration
2024-11-26 16:40:38 +01:00
Fabian Gruber f0b6c729c9 feat: add more tags to sentry 2024-11-26 16:13:55 +01:00
fogelito 135ff1a724 bomb migration 0.6.13 2024-11-26 17:05:17 +02:00
fogelito 442459f6a4 fix restoration 2024-11-26 16:14:38 +02:00
Christy JacobandGitHub 74c8a664a8 Merge pull request #8838 from appwrite/PLA-1791
feat: move certificate generation to Adapter
2024-11-26 14:24:12 +01:00
Fabian Gruber 98984a8085 refactor: use single adapter 2024-11-26 13:54:52 +01:00
Fabian Gruber 3a9ba8a6ad feat: move certificate generation to Adapter 2024-11-26 13:54:52 +01:00
Christy JacobandGitHub ce35567c05 Merge pull request #9002 from appwrite/fix-project-accessed-at
Fix: update project last activity on init
2024-11-26 13:18:55 +01:00
Damodar Lohani 3a21ed6333 Merge remote-tracking branch 'origin/1.6.x' into fix-project-accessed-at 2024-11-26 11:51:50 +00:00
Damodar Lohani 580a9f331f fix user update 2024-11-26 08:59:03 +00:00
Christy JacobandGitHub 33fad2cb23 Merge pull request #9020 from appwrite/chore-bump-console-5.2.25
Bump console to version 5.2.25
2024-11-25 17:24:25 +01:00
Christy JacobandGitHub 533009c1da Merge branch '1.6.x' into chore-bump-console-5.2.25 2024-11-25 17:21:52 +01:00
Steven NguyenandGitHub cb385674a5 Add 1.6.1 to CHANGES.md 2024-11-25 15:58:22 +00:00
Steven NguyenandGitHub fb4c71aebb Merge pull request #9025 from appwrite/fix-tasks-warning
Fix: swoole task warning
2024-11-25 07:07:09 -08:00
Matej Bačo 9d3bc1f80a Fix swoole task warning 2024-11-25 11:04:04 +01:00
Steven NguyenandGitHub 8da2b249b7 Merge pull request #9019 from appwrite/fix-8569-users-create-session-secret
Fix users create session secret
2024-11-22 21:55:28 -08:00
Steven NguyenandGitHub 07b3cbaec9 Bump appwrite version to 1.6.1 2024-11-22 22:47:02 +00:00
Steven NguyenandGitHub 98d78163a2 Bump console to version 5.2.25 2024-11-22 22:39:24 +00:00
Steven NguyenandGitHub 014c613c93 fix: update secret returned from users.createSession()
1. Include at least 1 factor because the minumum number of factors
   required when mfa is disabled is 1.
2. Purge the cached user document to ensure the new session is included
   in subsequent requests for the user.
3. Fix the encoding of the secret to match other parts of the codebase.
2024-11-22 21:52:27 +00:00
Steven NguyenandGitHub 54ec39f513 Merge pull request #9016 from appwrite/feat-safe-workers
Feat: Safe workers
2024-11-22 11:07:18 -08:00
Steven NguyenandGitHub ad235610da Merge pull request #9018 from appwrite/rules-backwards-compatibility
chore: backwards compatibility for 1.6.x
2024-11-22 09:53:09 -08:00
Christy Jacob ae7cb36240 chore: linter 2024-11-22 17:18:23 +00:00
Christy Jacob 33dd4df694 chore: backwards compatibility for 1.6.x 2024-11-22 17:16:51 +00:00
Matej Bačo 2fcfd1d7c9 Add safe workers 2024-11-22 13:57:42 +00:00
Christy JacobandGitHub 7ccc74bae2 Merge pull request #9006 from appwrite/fix-team-invite-session-creation
fix: team invites with existing session
2024-11-22 09:38:40 +01:00
Christy JacobandGitHub 44a0469d18 Merge branch '1.6.x' into fix-team-invite-session-creation 2024-11-22 09:21:54 +01:00
Christy JacobandGitHub 34d8a0504b Merge pull request #9013 from appwrite/feat-db-create-on-server-start
Improved shared tables V2
2024-11-21 10:41:52 +01:00
Jake Barnby f9e518b2b0 Fix shared tables v2 deletes 2024-11-21 16:49:49 +13:00
Jake Barnby 8410710005 Remove redundant DSN construction 2024-11-21 16:47:12 +13:00
Jake Barnby 018c865d04 Skip all project db calls on project create if shared tables v2 2024-11-21 16:46:36 +13:00
Jake Barnby 415dedc135 Create V2 shared tables databases on server start 2024-11-21 16:46:10 +13:00
loks0nandJake Barnby d257fdb045 chore: remove redundant compression 2024-11-21 14:24:56 +13:00
Steven NguyenandJake Barnby f6e7c12b02 Merge pull request #8922 from appwrite/lohanidamodar-patch-4
Improve compression param checks
2024-11-21 14:17:52 +13:00
Christy JacobandJake Barnby 908adebf74 Merge pull request #8964 from appwrite/debug-whitelist-console
debug: whitelist console
2024-11-21 14:11:20 +13:00
Christy JacobandJake Barnby 6e6899c1e9 Merge pull request #8988 from appwrite/fix-deletes
Fix project type delete checks
2024-11-21 14:09:02 +13:00
Christy JacobandJake Barnby f490d4edaf Merge pull request #8797 from appwrite/feat-migration
Feat migration
# Conflicts:
#	composer.lock
2024-11-21 14:08:20 +13:00
Torsten Dittmann 0b11e0cc92 fix: gitignore merge conflict leftover 2024-11-20 11:52:27 +01:00
Torsten DittmannandGitHub a5d43ddf71 Merge pull request #9011 from appwrite/sync-1.6.x
sync: 1.6.x
2024-11-20 11:48:37 +01:00
Torsten Dittmann 15a5f8690d sync: 1.6.x with main 2024-11-20 11:33:38 +01:00
Torsten Dittmann 9901c781b7 Merge branch 'main' of https://github.com/appwrite/appwrite into sync-1.6.x 2024-11-20 11:31:52 +01:00
Damodar Lohani c33c5732e0 move accessed at to init 2024-11-20 04:29:57 +00:00
Damodar Lohani 2da2b5bb7f reset error hook 2024-11-20 04:25:37 +00:00
Torsten Dittmann c97a94ec98 chore: run formatter 2024-11-19 17:18:51 +01:00
Torsten Dittmann 5f56c45c6c fix: team invites with existing session 2024-11-19 17:18:51 +01:00
Eldad A. FuxandGitHub ee3d34310a Merge pull request #9005 from appwrite/base-0.9.5
chore: update base to 0.9.5
2024-11-19 17:15:43 +01:00
Fabian Gruber 877a978451 chore: update base to 0.9.5 2024-11-19 16:42:16 +01:00
Damodar Lohani e8079cfd29 lint 2024-11-19 06:59:30 +00:00
Damodar LohaniandGitHub 842e23220a update project last activity on error hook 2024-11-19 07:51:50 +05:45
Matej BačoandGitHub b2cd20bf29 Merge pull request #8934 from appwrite/fix-bandwidth-calculation
Fix: bandwidth calculation
2024-11-18 11:16:51 +01:00
Jake BarnbyandGitHub d49b0540fd Merge pull request #8978 from appwrite/unset-index-length
Unset index length
2024-11-18 17:05:00 +13:00
Torsten DittmannandGitHub 8e02115828 ci: console sdk previews (#8990) 2024-11-15 13:27:46 +01:00
Steven NguyenandGitHub 3a748fbd35 Merge pull request #8972 from appwrite/fix-remove-duplicate-dart-216-runtime-template
Remove duplicate dart-2.16 runtime template
2024-11-14 12:33:33 -08:00
fogelito 7e7a4658cd revert lock 2024-11-14 14:20:28 +02:00
fogelito abf97e391e formatting 2024-11-14 14:19:33 +02:00
fogelito d8799e5a9c Remove comment 2024-11-14 14:13:28 +02:00
fogelito 217e471a41 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into unset-index-length 2024-11-14 14:12:03 +02:00
fogelito 72e491ac69 Remove unsetting index length for strings 2024-11-14 14:11:36 +02:00
Jake BarnbyandGitHub bdb0b188ef Merge pull request #8971 from appwrite/fix-alter-attributes
Fix Patch attribute
2024-11-14 20:55:59 +13:00
Jake BarnbyandGitHub 191f884772 Merge pull request #8959 from appwrite/fix-send-migration-errors-individually
Send migration errors individually
2024-11-14 19:57:27 +13:00
Steven NguyenandGitHub 45f85d6b4d fix: remove duplicate dart-2.16 runtime template 2024-11-13 11:30:48 -08:00
fogelito 8f845aef53 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into fix-alter-attributes
# Conflicts:
#	src/Appwrite/Platform/Workers/Databases.php
2024-11-13 18:34:57 +02:00
fogelito 68e96e9851 comment 2024-11-13 18:32:20 +02:00
Eldad A. FuxandGitHub c6cf27bd6f Merge pull request #8960 from appwrite/PLA-1854
feat: add telemetry
2024-11-13 14:08:45 +01:00
fogelito 71f2c6c131 Remove purgeCachedCollection 2024-11-13 14:55:56 +02:00
Fabian Gruber 1db71a46ea chore: use telemetry 0.1.0 2024-11-13 13:52:41 +01:00
Fabian Gruber 8124f6998e feat: add telemetry 2024-11-13 13:52:41 +01:00
Torsten DittmannandGitHub 7f06b0ec76 Merge pull request #8969 from appwrite/fix-memberships-privacy-mfa
fix: memberships privacy mfa
2024-11-13 12:36:57 +01:00
loks0n ca533f40c9 fix: memberships-privacy mfa only 2024-11-13 10:45:50 +01:00
Bradley Schofield 54db5655a1 Run Linter 2024-11-13 13:42:49 +09:00
Bradley Schofield 91cc0e627a Address Comments 2024-11-13 10:29:43 +09:00
Bradley Schofield f1af8b330e Merge remote-tracking branch 'origin/1.6.x' into fix-send-migration-errors-individually 2024-11-13 09:13:15 +09:00
Bradley Schofield 29e7d1f7d0 Address Comments 2024-11-13 09:13:00 +09:00
Steven NguyenandGitHub 97bcccba85 Merge pull request #8963 from appwrite/remove-inaccurate-doc
Remove inaccurate info about leaving the URL parameter empty
2024-11-12 15:43:56 -08:00
Ebenezer DonandGitHub 7f3a342940 Remove inaccurate info about leaving the URL parameter empty 2024-11-12 16:44:20 +00:00
Torsten DittmannandGitHub cca5606b70 Merge pull request #8943 from appwrite/feat-realtime-ping-pong
feat: realtime ping pong
2024-11-12 14:09:58 +01:00
Bradley Schofield 1337ea77b1 Fix Optionals 2024-11-12 16:18:09 +09:00
Bradley Schofield a460a41fe5 Run Linter 2024-11-12 11:33:13 +09:00
Bradley Schofield b62640489d Fire logs for each individual migration error 2024-11-12 11:29:19 +09:00
Jake BarnbyandGitHub f9e671419b Merge pull request #8944 from appwrite/database-worker-errors
Database worker errors
2024-11-12 13:23:24 +13:00
fogelito fcf018580c Merge branch '1.6.x' of https://github.com/appwrite/appwrite into database-worker-errors 2024-11-11 18:53:36 +02:00
fogelito 8cca88f2b8 Remove Sentry comment 2024-11-11 18:52:15 +02:00
fogelito bae6e465fa updateAttributes 2024-11-11 18:46:47 +02:00
Jake BarnbyandGitHub 3846903b9d Merge pull request #8956 from appwrite/fix-migration-id-null
Remove double fetch from migrations worker
2024-11-11 22:13:57 +13:00
Bradley Schofield d9e2ecff54 Remove get document from worker 2024-11-11 16:45:17 +09:00
fogelito 06e3afcf88 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into database-worker-errors 2024-11-10 16:42:34 +02:00
fogelito 0ef28eba17 Merge remote-tracking branch 'origin/database-worker-errors' into database-worker-errors 2024-11-10 16:38:04 +02:00
fogelito e5730506eb Remove var dumps 2024-11-10 16:37:44 +02:00
Christy JacobandGitHub 59c233b1e5 Merge pull request #8951 from appwrite/update-rules-fetching
feat: use getDocument instead of find() for rules
2024-11-09 17:00:28 +01:00
Christy Jacob 9334002214 Merge branch '1.6.x' of github.com:appwrite/appwrite into update-rules-fetching 2024-11-09 16:35:03 +01:00
Christy Jacob ba91d90c40 chore: linter 2024-11-09 16:31:33 +01:00
Christy Jacob 6c7f484383 chore: update more find methods 2024-11-09 16:18:22 +01:00
Christy Jacob e8ddda079d chore: update more find methods 2024-11-09 16:13:44 +01:00
Christy Jacob e04551ab6e feat: adjust more proxy queries 2024-11-09 11:43:52 +01:00
Christy Jacob e4cd914d57 feat: downgrade db library 2024-11-09 11:34:56 +01:00
Christy JacobandGitHub 275bccb9b1 Merge pull request #8948 from appwrite/fix-setpaused
fix: setpaused
2024-11-09 10:33:16 +01:00
Christy Jacob 6ae9952fa6 chore: linter 2024-11-08 22:18:05 +01:00
Christy Jacob 5f29af2c27 feat: use getDocument instead of find() for rules 2024-11-08 22:08:13 +01:00
Christy JacobandGitHub 3dff15f7cd Merge pull request #8949 from appwrite/revert-8946-chore-update-database
Revert "Update database"
2024-11-08 22:02:20 +01:00
Christy JacobandGitHub b1a1ad618a Revert "Update database" 2024-11-09 01:01:14 +04:00
loks0n af448ac183 feat: setpaused 2024-11-08 21:55:53 +01:00
Christy JacobandGitHub cf55bf95ce Merge pull request #8947 from appwrite/feat-configurable-compression-minimum-size
feat: configurable compression minimum size
2024-11-08 20:20:55 +01:00
loks0n 532aac6a62 chore: dockerfiles 2024-11-08 20:00:05 +01:00
loks0n 428998b6b6 chore: fix 2024-11-08 19:50:20 +01:00
loks0n d0cf0f6d1d feat: min size env 2024-11-08 19:48:32 +01:00
Christy JacobandGitHub ee2d0c0b25 Merge pull request #8938 from appwrite/feat-use-utopia-compression
feat: use utopia compression
2024-11-08 16:52:53 +01:00
loks0n d37c0500fc chore: bump framework, remove vender mount 2024-11-08 16:11:30 +01:00
loks0n cd817dbc0f chore: bump framework 2024-11-08 16:04:33 +01:00
loks0n 4b9457acc5 chore: bump compression 2024-11-08 16:03:32 +01:00
loks0n 56c43560f1 chore: composer update 2024-11-08 13:41:29 +01:00
loks0n fb42cbcade Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-use-utopia-compression 2024-11-08 13:39:13 +01:00
loks0n c8c4dd28df chore: composer update 2024-11-08 13:28:01 +01:00
loks0n e43887c53b feat: remove verbose 2024-11-08 13:06:09 +01:00
loks0n ee4b54857f fix: tests 2024-11-08 13:01:18 +01:00
Jake Barnby 3b82719b73 Merge remote-tracking branch 'origin/1.6.x' into database-worker-errors 2024-11-08 23:33:31 +13:00
Jake BarnbyandGitHub c0bd1664be Merge pull request #8946 from appwrite/chore-update-database
Update database
2024-11-08 23:32:45 +13:00
Jake Barnby 78c4bb395c Fix test 2024-11-08 21:59:42 +13:00
Jake Barnby 14532acb37 Update limit errors 2024-11-08 21:43:25 +13:00
Jake Barnby a17e722dc7 Update database 2024-11-08 20:19:40 +13:00
Jake BarnbyandGitHub b42dd86451 Merge pull request #8891 from appwrite/fix-xdebug-extension-enabled
Fix XDebug Extension not being removed
2024-11-08 18:12:36 +13:00
fogelito 06274764c6 Remove extra lines 2024-11-08 07:12:23 +02:00
Bradley Schofield d33056d13b Address Comments 2024-11-08 13:48:41 +09:00
loks0n 83cbddb926 fix: test 2024-11-07 20:31:12 +01:00
Christy JacobandGitHub d3bb5d963b Merge pull request #8913 from appwrite/feat-list-memberships-as-client
feat: list memberships as client
2024-11-07 20:28:46 +01:00
loks0n 21e946e9b6 fix: tests 2024-11-07 19:12:41 +01:00
loks0n 33a441421e chore: enabled privacy by default 2024-11-07 18:23:13 +01:00
loks0n 71eba6a879 fix: tests 2024-11-07 18:21:41 +01:00
loks0n e212481262 chore: bump composer 2024-11-07 16:50:33 +01:00
fogelito fb40970eba line 2024-11-07 17:35:03 +02:00
fogelito cd0d1d94db Add to finally 2024-11-07 17:27:56 +02:00
fogelito 19667f78a9 Add try catch on delete Attribute 2024-11-07 17:23:46 +02:00
loks0n 31062aa3ee fix: specs 2024-11-07 15:54:49 +01:00
loks0n 25adb39634 chore: update model 2024-11-07 15:37:34 +01:00
loks0n c19d6a6dda chore: description 2024-11-07 15:33:44 +01:00
fogelito a6779caa5e Trigger sentry 2024-11-07 14:28:39 +02:00
Torsten Dittmann cddd16b753 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-realtime-ping-pong 2024-11-07 13:10:23 +01:00
Torsten Dittmann f91f382baf style: remove unnecessary comment 2024-11-07 12:08:11 +01:00
Torsten Dittmann 4ee9e9730f feat: realtime ping pong 2024-11-07 12:05:37 +01:00
loks0n 5b4d51f6dd chore: composer update 2024-11-07 11:30:59 +01:00
loks0n e7e1a5b11f fix: tests 2024-11-07 11:13:20 +01:00
loks0n d4de44429f fix: tests 2024-11-07 10:33:40 +01:00
loks0n b7397617e6 fix: tests 2024-11-07 10:19:22 +01:00
loks0n afbdca3fc3 chore: specs 2024-11-06 21:21:38 +01:00
loks0n 99565cae99 feat: improve copy 2024-11-06 21:21:21 +01:00
loks0n e4c6e97ded feat: memberships privacy 2024-11-06 21:15:31 +01:00
loks0n 35c2978b74 fix: test warning 2024-11-06 19:01:18 +01:00
loks0n ce36392a37 chore: bump framework 2024-11-06 18:56:40 +01:00
loks0n a6ae565093 fix: tests 2024-11-06 17:54:02 +01:00
Matej Bačo c9e6ef8202 Rename based on PR reviews 2024-11-06 16:05:58 +00:00
loks0n 617004c914 Merge branch 'feat-list-memberships-as-client' of https://github.com/appwrite/appwrite into feat-list-memberships-as-client 2024-11-06 16:52:41 +01:00
loks0n ff711d5ff9 chore: update specs 2024-11-06 16:52:37 +01:00
Luke B. SilverandGitHub bb174b19b4 Merge branch '1.6.x' into feat-list-memberships-as-client 2024-11-06 16:49:08 +01:00
loks0n cb70a51adb fix: tests 2024-11-06 16:35:31 +01:00
loks0n 7fd5f8c934 docs 2024-11-06 16:00:27 +01:00
loks0n 12b60e01cf fix: tests 2024-11-06 15:51:15 +01:00
Matej Bačo 87cfd7df3b Remove leftover 2024-11-06 11:39:44 +00:00
Christy JacobandGitHub 0a8f62fd09 Merge pull request #8905 from appwrite/redis-cluster
feat: PubSub adapter support
2024-11-06 12:38:01 +01:00
Matej Bačo ca887f018e fix domain execution response size 2024-11-06 11:29:24 +00:00
Fabian Gruber b410b17ba6 feat: redis-cluster support 2024-11-06 12:27:03 +01:00
Matej Bačo 54c24b24e6 Improve preview domains local development flow 2024-11-06 10:52:43 +00:00
Jake BarnbyandGitHub d7c64b6557 Merge pull request #8929 from appwrite/validate-string-permissions
Validate string permissions
2024-11-06 22:12:05 +13:00
fogelito 231c181dc8 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into validate-string-permissions 2024-11-06 09:41:44 +02:00
fogelito 46af6dc3f0 Fix permissions 2024-11-06 09:36:27 +02:00
Jake BarnbyandGitHub a24fb66909 Merge pull request #8927 from appwrite/chore-update-database
Update database for transaction counter fixes with retries
2024-11-06 16:47:31 +13:00
Jake Barnby 59674fa908 Update database 2024-11-06 16:32:31 +13:00
Jake Barnby 90f63a30f6 Revert "Fix trivy scans"
This reverts commit 0f6aa3d5b1.
2024-11-06 16:24:55 +13:00
Jake Barnby 0f6aa3d5b1 Fix trivy scans 2024-11-06 15:45:26 +13:00
Jake Barnby c6b297dc82 Update database for transaction counter fixes with retries 2024-11-06 15:05:04 +13:00
loks0n d27a7189c7 fix: stash 2024-11-05 19:46:22 +01:00
Christy JacobandGitHub 51274ba709 Merge pull request #8718 from appwrite/fix-oauth-trigger-create-user-event
fix: trigger create user event
2024-11-05 19:39:13 +01:00
loks0n 0e254618f6 fix: tests 2024-11-05 18:37:48 +01:00
loks0n 6bd641d70e fix: default 2024-11-05 18:11:06 +01:00
loks0n bd94025402 fix: typo 2024-11-05 18:07:08 +01:00
loks0n 6fd54d646a fix: tests 2024-11-05 18:05:33 +01:00
loks0n 9d1f366161 fix: console 2024-11-05 17:59:14 +01:00
loks0n 71879236b8 fix: disable test 2024-11-05 17:42:01 +01:00
loks0n 8ecc2058a1 fix: tests 2024-11-05 17:19:56 +01:00
Luke B. SilverandGitHub 65fa578c3a Merge branch '1.6.x' into feat-list-memberships-as-client 2024-11-05 17:07:00 +01:00
loks0n 69e8fdf077 chore: refactor 2024-11-05 17:05:21 +01:00
loks0n 229fe9ecd5 fix: tests 2024-11-05 15:50:32 +01:00
loks0n ab7e454292 fix: event name 2024-11-05 15:17:41 +01:00
loks0n 2dea9b1f51 chore: clone 2024-11-05 14:35:35 +01:00
Luke B. SilverandGitHub 5c9d81f659 Merge branch '1.6.x' into fix-oauth-trigger-create-user-event 2024-11-05 14:24:02 +01:00
Jake BarnbyandGitHub 477d6b52be Merge pull request #8923 from appwrite/chore-update-database
Update database
2024-11-05 23:38:03 +13:00
Jake Barnby 540e997fdb Update database 2024-11-05 23:15:08 +13:00
loks0n e9a0f47711 fix: event test 2024-11-05 09:55:56 +01:00
loks0n 20d8a702a1 chore: refactor 2024-11-04 15:20:43 +00:00
Luke B. SilverandGitHub 1ff21034a9 Merge branch '1.6.x' into fix-oauth-trigger-create-user-event 2024-11-04 15:07:31 +00:00
loks0n d2deca7f1f feat: refactor events 2024-11-04 15:05:54 +00:00
Jake BarnbyandGitHub 5d4ae8075a Merge pull request #8917 from appwrite/chore-update-database
Update database
2024-11-04 22:43:42 +13:00
Jake Barnby 4489d5f77f Fix validator 2024-11-04 22:13:38 +13:00
Jake Barnby a00671fe29 Lint 2024-11-04 21:53:00 +13:00
Jake Barnby 6bba134650 Set min/max for datetime validator 2024-11-04 21:50:22 +13:00
Jake Barnby de2ce16f10 Fix non-variable passed by reference 2024-11-04 21:49:20 +13:00
Jake Barnby b6287e11a3 Add storage catches 2024-11-04 21:40:02 +13:00
Jake Barnby c59cd97eb8 Not found exception 2024-11-04 20:57:08 +13:00
Jake Barnby ca5d9ce96a Update database 2024-11-04 15:09:16 +13:00
loks0n ff95532ccb chore: refactor 2024-11-01 16:29:34 +00:00
loks0n e87ed0e13a chore: refactor use from over clonse 2024-11-01 15:24:29 +00:00
loks0n 4c14375d89 fix: tests 2024-11-01 13:31:33 +00:00
loks0n 37d60e1039 fix: var name 2024-11-01 13:28:13 +00:00
loks0n f5e2f4acfa fix: endpoint name 2024-11-01 12:53:54 +00:00
loks0n c9b2d6eb90 feat: teamHideSensitiveFields 2024-11-01 11:38:56 +00:00
Luke B. SilverandGitHub ab3b7d3028 Merge branch '1.6.x' into fix-oauth-trigger-create-user-event 2024-10-31 10:39:42 +00:00
loks0n 51ae3a6475 fix: tests 2024-10-30 18:31:14 +00:00
loks0n 2736f6009a feat: dirty solution 2024-10-30 18:18:37 +00:00
loks0n 401996e7c2 Merge branch 'fix-oauth-trigger-create-user-event' of https://github.com/appwrite/appwrite into fix-oauth-trigger-create-user-event 2024-10-30 18:01:24 +00:00
loks0n 26c53bcf9c feat: clone queue 2024-10-30 18:01:12 +00:00
Luke B. SilverandGitHub 1594dc8925 Merge branch '1.6.x' into fix-oauth-trigger-create-user-event 2024-10-30 11:45:38 +00:00
Luke B. SilverandGitHub e89603c479 Merge branch '1.6.x' into fix-oauth-trigger-create-user-event 2024-10-29 12:18:21 +00:00
loks0n 0b35d51547 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into fix-oauth-trigger-create-user-event 2024-10-29 12:16:47 +00:00
loks0n 9d4fd41701 fix: webhook test 2024-10-29 11:56:41 +00:00
loks0n 13d1376e28 fix: type 2024-10-29 11:20:52 +00:00
loks0n 3020c0beea fix: missing dep 2024-10-29 11:01:29 +00:00
loks0n d16251d261 fix: remove old create user events 2024-10-29 10:58:57 +00:00
loks0n 5afa8c6158 feat: usage db listener 2024-10-29 10:51:40 +00:00
Bradley Schofield 943a3a7fdd Remove correct version of XDebug 2024-10-29 17:20:15 +09:00
Luke B. SilverandGitHub 61627337fa Merge branch 'main' into fix-oauth-trigger-create-user-event 2024-10-28 18:40:21 +00:00
Luke B. SilverandGitHub 36368f3d80 Merge branch 'main' into fix-oauth-trigger-create-user-event 2024-09-25 11:26:58 +01:00
loks0n 3ba7d7c7de fix: add payload 2024-09-25 10:48:39 +01:00
loks0n ab5ef97767 fix: oauth trigger create user event 2024-09-25 10:39:11 +01:00
659 changed files with 27057 additions and 27214 deletions
+5
View File
@@ -2,6 +2,7 @@ _APP_ENV=development
_APP_EDITION=self-hosted
_APP_LOCALE=en
_APP_WORKER_PER_CORE=6
_APP_COMPRESSION_MIN_SIZE_BYTES=1024
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_SESSION_ALERTS=enabled
@@ -22,6 +23,7 @@ _APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_TARGET=localhost
_APP_RULES_FORMAT=md5
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
_APP_REDIS_PASS=
@@ -84,6 +86,7 @@ _APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_USAGE_AGGREGATION_INTERVAL=30
_APP_STATS_RESOURCES_INTERVAL=3600
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_USAGE_STATS=enabled
@@ -107,3 +110,5 @@ _APP_MESSAGE_EMAIL_TEST_DSN=
_APP_MESSAGE_PUSH_TEST_DSN=
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_PROJECT_REGIONS=default
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
+33
View File
@@ -0,0 +1,33 @@
name: "Console SDK Preview"
on:
pull_request:
paths:
- 'app/config/specs/*-latest-console.json'
jobs:
setup:
name: Setup & Build Console SDK
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Load and Start Appwrite
run: |
docker compose build
docker compose up -d
docker compose exec appwrite sdks --platform=console --sdk=web --version=latest --git=no
sudo chown -R $USER:$USER ./app/sdks/console-web
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Build and Publish SDK
working-directory: ./app/sdks/console-web
run: |
npm install
npm run build
npx pkg-pr-new publish --comment=update
+105 -6
View File
@@ -8,9 +8,33 @@ env:
IMAGE: appwrite-dev
CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }}
on: [pull_request]
on: [ pull_request ]
jobs:
check_database_changes:
name: Check if utopia-php/database changed
runs-on: ubuntu-latest
outputs:
database_changed: ${{ steps.check.outputs.database_changed }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Fetch base branch
run: git fetch origin ${{ github.event.pull_request.base.ref }}
- name: Check for utopia-php/database changes
id: check
run: |
if git diff origin/${{ github.event.pull_request.base.ref }} HEAD -- composer.lock | grep -q '"name": "utopia-php/database"'; then
echo "Database version changed, going to run all mode tests."
echo "database_changed=true" >> "$GITHUB_ENV"
echo "database_changed=true" >> "$GITHUB_OUTPUT"
else
echo "database_changed=false" >> "$GITHUB_ENV"
echo "database_changed=false" >> "$GITHUB_OUTPUT"
fi
setup:
name: Setup & Build Appwrite Image
runs-on: ubuntu-latest
@@ -103,6 +127,62 @@ jobs:
name: E2E Service Test
runs-on: ubuntu-latest
needs: setup
strategy:
fail-fast: false
matrix:
service: [
Account,
Avatars,
Console,
Databases,
Functions,
FunctionsSchedule,
GraphQL,
Health,
Locale,
Projects,
Realtime,
Storage,
Teams,
Users,
Webhooks,
VCS,
Messaging,
Migrations
]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
fail-on-cache-miss: true
- name: Load and Start Appwrite
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 30
- name: Run ${{ matrix.service }} tests with Project table mode
run: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug
e2e_shared_mode_test:
name: E2E Shared Mode Service Test
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
strategy:
fail-fast: false
matrix:
@@ -127,6 +207,10 @@ jobs:
Messaging,
Migrations
]
tables-mode: [
'Shared V1',
'Shared V2',
]
steps:
- name: checkout
@@ -145,16 +229,29 @@ jobs:
docker compose up -d
sleep 30
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
- name: Run ${{matrix.service}} Shared Tables Tests
run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
- name: Run ${{ matrix.service }} tests with ${{ matrix.tables-mode }} table mode
run: |
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
fi
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug
benchmarking:
name: Benchmark
runs-on: ubuntu-latest
needs: setup
permissions:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -215,6 +312,7 @@ jobs:
path: benchmark.json
retention-days: 7
- name: Find Comment
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/find-comment@v3
id: fc
with:
@@ -222,6 +320,7 @@ jobs:
comment-author: 'github-actions[bot]'
body-includes: Benchmark results
- name: Comment on PR
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
+1
View File
@@ -16,3 +16,4 @@ dev/yasd_init.php
.phpunit.result.cache
Makefile
appwrite.json
/.zed/
+114
View File
@@ -1,3 +1,117 @@
# Version 1.6.1
## What's Changed
### Notable changes
* Remove JPEG fallback for webp by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8746
* Add heic and avif support by @lohanidamodar in https://github.com/appwrite/appwrite/pull/7718
* Add new runtimes by @Meldiron in https://github.com/appwrite/appwrite/pull/8771
* Remove audits deletion by @shimonewman in https://github.com/appwrite/appwrite/pull/8766
* Bump assistant by @loks0n in https://github.com/appwrite/appwrite/pull/8801
* Change max queries values to 500 by @fogelito in https://github.com/appwrite/appwrite/pull/8802
* Allow '.wav' as 'audio/x-wav' as well by @basert in https://github.com/appwrite/appwrite/pull/8846
* Use 1 instead of 0.5 cpu for default function specification by @loks0n in https://github.com/appwrite/appwrite/pull/8848
* Update function runtimes by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8781
* Add a realtime heartbeat by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8943
### Fixes
* Trigger functions event only if event is not paused by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8526
* Update docker-compose to restart usage-dump by @feschaffa in https://github.com/appwrite/appwrite/pull/8642
* Fix typo in scheduler base by @fogelito in https://github.com/appwrite/appwrite/pull/8691
* Add domain and force HTTPS env vars to mail worker by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8722
* Fix webp by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8732
* Ignore junction tables by @fogelito in https://github.com/appwrite/appwrite/pull/8728
* Fix logger throwing fatal error by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8724
* Fix missing protocol for testing SMTP by @byawitz in https://github.com/appwrite/appwrite/pull/8749
* Make create execution async loose by @loks0n in https://github.com/appwrite/appwrite/pull/8707
* Fix invalid cursor value by @fogelito in https://github.com/appwrite/appwrite/pull/8109
* Fix target deletes by @abnegate in https://github.com/appwrite/appwrite/pull/8833
* Fix translation commas by @loks0n in https://github.com/appwrite/appwrite/pull/8892
* Fix Migrations having source creds being overwritten and add Migration tests by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8897
* Fix validator usage for updating string size by @abnegate in https://github.com/appwrite/appwrite/pull/8890
* Fix create user event not triggering by @loks0n in https://github.com/appwrite/appwrite/pull/8718
* Improve error handling and logging in the database worker by @fogelito in https://github.com/appwrite/appwrite/pull/8944
* Remove inaccurate info about leaving the URL parameter empty by @ebenezerdon in https://github.com/appwrite/appwrite/pull/8963
* Ensure indexes are updated when updating an attribute key by @fogelito in https://github.com/appwrite/appwrite/pull/8971
* Remove duplicate dart-2.16 runtime template by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8972
* Fix team invites with existing session by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9006
* Improve handling of HTTP requests by dispatching to safe workers by @Meldiron in https://github.com/appwrite/appwrite/pull/9016
* Fix users create session secret by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9019
* Fix swoole task warning by @Meldiron in https://github.com/appwrite/appwrite/pull/9025
### Miscellaneous
* Update Init copy by @adityaoberai in https://github.com/appwrite/appwrite/pull/8557
* Fix security scan permissions and comment by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/8525
* Add Trivy security scans by @btme0011 in https://github.com/appwrite/appwrite/pull/6876
* Update database stack by @abnegate in https://github.com/appwrite/appwrite/pull/8564
* Bump database by @abnegate in https://github.com/appwrite/appwrite/pull/8573
* Sync main with 1.5.x by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8589
* Add AWS to one-click installs by @byawitz in https://github.com/appwrite/appwrite/pull/8593
* Update Init copy in readme by @adityaoberai in https://github.com/appwrite/appwrite/pull/8618
* Sync main into 1.6.x by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8685
* Sync 1.6.x into main by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8686
* Feat coroutines by @Meldiron in https://github.com/appwrite/appwrite/pull/7826
* Sync main into 1.6.x by @Meldiron in https://github.com/appwrite/appwrite/pull/8719
* Sentence casing endpoint API reference by @choir241 in https://github.com/appwrite/appwrite/pull/8617
* DB storage metrics by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8404
* Fix exception thrown when optional array attribute does not exist by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8391
* Add projects channels to realtime by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8735
* Base for console roles support by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8565
* Remove DB disk storage calculation by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8745
* Messaging adapter default values by @shimonewman in https://github.com/appwrite/appwrite/pull/8742
* Add payload response type by @loks0n in https://github.com/appwrite/appwrite/pull/8720
* Fix flaky functions tests by @loks0n in https://github.com/appwrite/appwrite/pull/8682
* Migrations Backups by @fogelito in https://github.com/appwrite/appwrite/pull/8186
* Add test for response and request filters by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8697
* Bump version in SECURITY.md by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/8755
* Add originalId attribute to databases collection by @fogelito in https://github.com/appwrite/appwrite/pull/8764
* Fix Walter References by @ItzNotABug in https://github.com/appwrite/appwrite/pull/8757
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8769
* Move new attributes by @abnegate in https://github.com/appwrite/appwrite/pull/8777
* Add ping endpoint by @loks0n in https://github.com/appwrite/appwrite/pull/8761
* Fix GitHub action caching by @loks0n in https://github.com/appwrite/appwrite/pull/8772
* Chore release ruby SDK by @abnegate in https://github.com/appwrite/appwrite/pull/8767
* Call migration success on success by @abnegate in https://github.com/appwrite/appwrite/pull/8782
* Update utopia-php/system to 0.9.0 by @basert in https://github.com/appwrite/appwrite/pull/8780
* Move createDocument from api to worker by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8776
* Add missing indexes by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8803
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8809
* Fix typo in BLR region by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8756
* Add tests for project variables by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8815
* Replace 'Expires' with 'Cache-Control: private' header to avoid CDN caching by @basert in https://github.com/appwrite/appwrite/pull/8836
* Allow blocking based on resource attributes by @basert in https://github.com/appwrite/appwrite/pull/8812
* Check if resource is blocked inside functions worker by @basert in https://github.com/appwrite/appwrite/pull/8855
* Fix missing allow attribute by @abnegate in https://github.com/appwrite/appwrite/pull/8889
* Revert function execution order by @basert in https://github.com/appwrite/appwrite/pull/8857
* Use resource type constants by @basert in https://github.com/appwrite/appwrite/pull/8895
* Update Database lib by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8680
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8917
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8923
* Update database for transaction counter fixes with retries by @abnegate in https://github.com/appwrite/appwrite/pull/8927
* Validate string permissions by @fogelito in https://github.com/appwrite/appwrite/pull/8929
* Add PubSub adapter support by @basert in https://github.com/appwrite/appwrite/pull/8905
* List memberships as client by @loks0n in https://github.com/appwrite/appwrite/pull/8913
* Fix XDebug Extension not being removed by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8891
* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8946
* Use utopia compression by @loks0n in https://github.com/appwrite/appwrite/pull/8938
* Make compression minimum size configurable by @loks0n in https://github.com/appwrite/appwrite/pull/8947
* Revert "Update database" by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8949
* Fix setpaused by @loks0n in https://github.com/appwrite/appwrite/pull/8948
* Use getDocument instead of find() for rules by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8951
* Remove double fetch from migrations worker by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8956
* Fix memberships privacy MFA by @loks0n in https://github.com/appwrite/appwrite/pull/8969
* Add telemetry by @basert in https://github.com/appwrite/appwrite/pull/8960
* Send migration errors individually by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8959
* Add console sdk previews by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8990
* Unset index length by @fogelito in https://github.com/appwrite/appwrite/pull/8978
* Update base to 0.9.5 by @basert in https://github.com/appwrite/appwrite/pull/9005
* Sync main into 1.6.x by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9011
* Improved shared tables V2 by @abnegate in https://github.com/appwrite/appwrite/pull/9013
* Ensure backwards compatibility for 1.6.x by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9018
# Version 1.6.0
## What's Changed
+18 -6
View File
@@ -367,7 +367,7 @@ In file `app/controllers/shared/api.php` On the database listener, add to an exi
```php
case $document->getCollection() === 'teams':
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_TEAMS, $value); // per project
break;
```
@@ -379,10 +379,10 @@ In that case you need also to handle children removal using addReduce() method c
```php
case $document->getCollection() === 'buckets': //buckets
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
$queueForStatsUsage
->addReduce($document);
}
break;
@@ -428,16 +428,16 @@ public function __construct()
->inject('dbForProject')
->inject('queueForFunctions')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('queueForStatsUsage')
->inject('log')
->callback(fn (Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log) => $this->action($message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForUsage, $log));
->callback(fn (Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log) => $this->action($message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $log));
}
```
and then trigger the queue with the new metric like so:
```php
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_BUILDS, 1)
->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0))
->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000)
@@ -613,6 +613,18 @@ If you need to clear the cache, you can do so by running the following command:
docker compose exec redis redis-cli FLUSHALL
```
## Using preview domains locally
Appwrite Functions are automatically given a domain you can visit to execute the function. This domain has format `[SOMETHING].functions.localhost` unless you changed `_APP_DOMAIN_FUNCTIONS` environment variable. This default value works great when running Appwrite locally, but it can be impossible to use preview domains with Cloud woekspaces such as Gitpod or GitHub Codespaces.
To use preview domains on Cloud workspaces, you can visit hostname provided by them, and supply function's preview domain as URL parameter:
```
https://8080-appwrite-appwrite-mjeb3ebilwv.ws-eu116.gitpod.io/ping?preview=672b3c7eab1ac523ccf5.functions.localhost
```
The path was set to `/ping` intentionally. Visiting `/` for preview domains might trigger Console background worker, and trigger redirect to Console without our preview URL param. Visiting different path ensures this doesnt happen.
## Tutorials
From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials:
+8 -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.3 AS final
FROM appwrite/base:0.9.5 AS final
LABEL maintainer="team@appwrite.io"
@@ -85,6 +85,10 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-messaging && \
chmod +x /usr/local/bin/worker-migrations && \
chmod +x /usr/local/bin/worker-webhooks && \
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
@@ -92,10 +96,10 @@ RUN chmod +x /usr/local/bin/doctor && \
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
# Enable Extensions
RUN if [ "$DEBUG" == "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi
RUN if [ "$DEBUG" == "true" ]; then mkdir -p /tmp/xdebug; fi
RUN if [ "$DEBUG" = "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi
RUN if [ "$DEBUG" = "true" ]; then mkdir -p /tmp/xdebug; fi
RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi
RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so; fi
RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xdebug.so; fi
EXPOSE 80
+3 -3
View File
@@ -67,7 +67,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.6.0
appwrite/appwrite:1.6.1
```
### Windows
@@ -79,7 +79,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.6.0
appwrite/appwrite:1.6.1
```
#### PowerShell
@@ -89,7 +89,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.6.0
appwrite/appwrite:1.6.1
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+3 -3
View File
@@ -75,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.6.0
appwrite/appwrite:1.6.1
```
### Windows
@@ -87,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.6.0
appwrite/appwrite:1.6.1
```
#### PowerShell
@@ -97,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.6.0
appwrite/appwrite:1.6.1
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+67 -25
View File
@@ -5,6 +5,8 @@ require_once __DIR__ . '/init.php';
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Func;
use Appwrite\Event\StatsResources;
use Appwrite\Event\StatsUsage;
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
use Utopia\Cache\Adapter\Sharding;
@@ -19,7 +21,7 @@ use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Platform\Service;
use Utopia\Pools\Group;
use Utopia\Queue\Connection;
use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\System\System;
@@ -52,7 +54,7 @@ CLI::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
CLI::setResource('dbForConsole', function ($pools, $cache) {
CLI::setResource('dbForPlatform', function ($pools, $cache) {
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
@@ -67,9 +69,9 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
->pop()
->getResource();
$dbForConsole = new Database($dbAdapter, $cache);
$dbForPlatform = new Database($dbAdapter, $cache);
$dbForConsole
$dbForPlatform
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
@@ -78,7 +80,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
$collections = Config::getParam('collections', [])['console'];
$last = \array_key_last($collections);
if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */
if (!($dbForPlatform->exists($dbForPlatform->getDatabase(), $last))) { /** TODO cache ready variable using registry */
throw new Exception('Tables not ready yet.');
}
@@ -94,15 +96,15 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
throw new Exception("Console is not ready yet. Please try again later.");
}
return $dbForConsole;
return $dbForPlatform;
}, ['pools', 'cache']);
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
return $dbForPlatform;
}
try {
@@ -114,8 +116,9 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -136,10 +139,10 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -157,20 +160,59 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
return $database;
};
}, ['pools', 'dbForConsole', 'cache']);
}, ['pools', 'dbForPlatform', 'cache']);
CLI::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
return $database;
}
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$database
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
}
return $database;
};
}, ['pools', 'cache']);
CLI::setResource('queueForStatsUsage', function (Connection $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
CLI::setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
CLI::setResource('publisher', function (Group $pools) {
return $pools->get('publisher')->pop()->getResource();
}, ['pools']);
CLI::setResource('queueForFunctions', function (Connection $queue) {
return new Func($queue);
}, ['queue']);
CLI::setResource('queueForDeletes', function (Connection $queue) {
return new Delete($queue);
}, ['queue']);
CLI::setResource('queueForCertificates', function (Connection $queue) {
return new Certificate($queue);
}, ['queue']);
CLI::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
CLI::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
CLI::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
CLI::setResource('logError', function (Registry $register) {
return function (Throwable $error, string $namespace, string $action) use ($register) {
$logger = $register->get('logger');
@@ -180,7 +222,7 @@ CLI::setResource('logError', function (Registry $register) {
$log = new Log();
$log->setNamespace($namespace);
$log->setServer(\gethostname());
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
+17 -6147
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
<?php
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
return [
'collections' => [
'$collection' => ID::custom('databases'),
'$id' => ID::custom('collections'),
'name' => 'Collections',
'attributes' => [
[
'$id' => ID::custom('databaseInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('databaseId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'size' => 256,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('enabled'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('documentSecurity'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[
'$id' => ID::custom('attributes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryAttributes'],
],
[
'$id' => ID::custom('indexes'),
'type' => Database::VAR_STRING,
'size' => 1000000,
'required' => false,
'signed' => true,
'array' => false,
'filters' => ['subQueryIndexes'],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_fulltext_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_documentSecurity'),
'type' => Database::INDEX_KEY,
'attributes' => ['documentSecurity'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
]
];
+94
View File
@@ -0,0 +1,94 @@
<?php
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
$logsCollection = [];
$logsCollection['stats'] = [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('stats'),
'name' => 'stats',
'attributes' => [
[
'$id' => ID::custom('metric'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('region'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('value'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 8,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('time'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('period'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 4,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_time'),
'type' => Database::INDEX_KEY,
'attributes' => ['time'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
],
[
'$id' => ID::custom('_key_period_time'),
'type' => Database::INDEX_KEY,
'attributes' => ['period', 'time'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_metric_period_time'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['metric', 'period', 'time'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
],
],
];
return $logsCollection;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -349,11 +349,6 @@ return [
'description' => 'Team with the requested ID could not be found.',
'code' => 404,
],
Exception::TEAM_INVITE_ALREADY_EXISTS => [
'name' => Exception::TEAM_INVITE_ALREADY_EXISTS,
'description' => 'User has already been invited or is already a member of this team',
'code' => 409,
],
Exception::TEAM_INVITE_NOT_FOUND => [
'name' => Exception::TEAM_INVITE_NOT_FOUND,
'description' => 'The requested team invitation could not be found.',
@@ -686,7 +681,7 @@ return [
],
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED,
'description' => 'The maximum number of attributes has been reached.',
'description' => 'The maximum number or size of attributes for this collection has been reached.',
'code' => 400,
],
Exception::ATTRIBUTE_VALUE_INVALID => [
@@ -731,6 +726,11 @@ return [
'description' => 'Index invalid.',
'code' => 400,
],
Exception::INDEX_DEPENDENCY => [
'name' => Exception::INDEX_DEPENDENCY,
'description' => 'Attribute cannot be renamed or deleted. Please remove the associated index first.',
'code' => 409,
],
/** Project Errors */
Exception::PROJECT_NOT_FOUND => [
+3 -2
View File
@@ -11,7 +11,7 @@ const TEMPLATE_RUNTIMES = [
],
'DART' => [
'name' => 'dart',
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16']
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
],
'GO' => [
'name' => 'go',
@@ -1567,7 +1567,8 @@ return [
'required' => false,
'type' => 'number'
]
]
],
'scopes' => []
],
[
'icon' => 'icon-chip',
+16 -16
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '16.0.2',
'version' => '16.1.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '13.0.0',
'version' => '13.1.1',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -77,7 +77,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '7.0.0',
'version' => '7.1.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -112,7 +112,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '6.0.0',
'version' => '6.1.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -134,7 +134,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.5.0',
'version' => '0.6.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '6.0.0',
'version' => '6.2.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -245,7 +245,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '14.1.0',
'version' => '14.2.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -263,7 +263,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '12.1.0',
'version' => '12.2.0',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@@ -281,7 +281,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '12.1.0',
'version' => '12.2.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -299,7 +299,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '6.1.0',
'version' => '6.2.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -317,7 +317,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '12.1.1',
'version' => '12.2.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -335,7 +335,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => '0.2.0',
'version' => '0.3.0',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
@@ -353,7 +353,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.10.1',
'version' => '0.11.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -371,7 +371,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '12.1.0',
'version' => '12.2.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -389,7 +389,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '6.1.0',
'version' => '6.2.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -411,7 +411,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '6.1.0',
'version' => '6.2.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+1 -1
View File
@@ -6,7 +6,7 @@ return [
Specification::S_05VCPU_512MB => [
'slug' => Specification::S_05VCPU_512MB,
'memory' => 512,
'cpus' => 1 // TODO: revert this, it's a temporary change to test function performance.
'cpus' => 0.5
],
Specification::S_1VCPU_512MB => [
'slug' => Specification::S_1VCPU_512MB,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -1,8 +1,11 @@
<?php
return [ // Accepted inputs files
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
return [
// Accepted inputs files
"jpg" => "image/jpeg",
"jpeg" => "image/jpeg",
"gif" => "image/gif",
"png" => "image/png",
"heic" => "image/heic",
"webp" => "image/webp",
];
+52 -51
View File
@@ -1,70 +1,71 @@
<?php
return [
'image/jpeg',
'image/jpeg',
'image/gif',
'image/png',
'image/webp',
// 'image/heic',
'image/avif',
"image/jpeg",
"image/jpeg",
"image/gif",
"image/png",
"image/webp",
"image/heic",
"image/heic-sequence",
"image/avif",
// Video Files
'video/mp4',
'video/x-flv',
'video/webm',
'application/x-mpegURL',
'video/MP2T',
'video/3gpp',
'video/quicktime',
'video/x-msvideo',
'video/x-ms-wmv',
"video/mp4",
"video/x-flv",
"video/webm",
"application/x-mpegURL",
"video/MP2T",
"video/3gpp",
"video/quicktime",
"video/x-msvideo",
"video/x-ms-wmv",
// Audio Files
'audio/basic', // au snd RFC 2046
'auido/L24', // Linear PCM RFC 3190
'audio/mid', // mid rmi
'audio/mpeg', // mp3 RFC 3003
'audio/mp4', // mp4 audio
'audio/x-aiff', // aif aifc aiff
'audio/x-mpegurl', // m3u
'audio/vnd.rn-realaudio', // ra ram
'audio/ogg', // Ogg Vorbis RFC 5334
'audio/vorbis', // Vorbis RFC 5215
'audio/vnd.wav', // wav RFC 2361
'audio/x-wav', // php reads .wav as this - https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
'audio/aac', //AAC audio
'audio/x-hx-aac-adts', // AAC audio
"audio/basic", // au snd RFC 2046
"auido/L24", // Linear PCM RFC 3190
"audio/mid", // mid rmi
"audio/mpeg", // mp3 RFC 3003
"audio/mp4", // mp4 audio
"audio/x-aiff", // aif aifc aiff
"audio/x-mpegurl", // m3u
"audio/vnd.rn-realaudio", // ra ram
"audio/ogg", // Ogg Vorbis RFC 5334
"audio/vorbis", // Vorbis RFC 5215
"audio/vnd.wav", // wav RFC 2361
"audio/x-wav", // php reads .wav as this - https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
"audio/aac", //AAC audio
"audio/x-hx-aac-adts", // AAC audio
// Microsoft Word
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/vnd.ms-word.document.macroEnabled.12',
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
"application/vnd.ms-word.document.macroEnabled.12",
// Microsoft Excel
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.ms-excel.template.macroEnabled.12',
'application/vnd.ms-excel.addin.macroEnabled.12',
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.ms-excel.sheet.macroEnabled.12",
"application/vnd.ms-excel.template.macroEnabled.12",
"application/vnd.ms-excel.addin.macroEnabled.12",
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
// Microsoft Power Point
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.openxmlformats-officedocument.presentationml.template',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'application/vnd.ms-powerpoint.addin.macroEnabled.12',
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'application/vnd.ms-powerpoint.template.macroEnabled.12',
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.template",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
"application/vnd.ms-powerpoint.addin.macroEnabled.12",
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
"application/vnd.ms-powerpoint.template.macroEnabled.12",
"application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
// Microsoft Access
'application/vnd.ms-access',
"application/vnd.ms-access",
// Adobe PDF
'application/pdf',
"application/pdf",
];
+9 -9
View File
@@ -1,12 +1,12 @@
<?php
return [ // Accepted outputs files
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'webp' => 'image/webp',
// 'heic' => 'image/heic',
// 'heics' => 'image/heic',
'avif' => 'image/avif'
return [
// Accepted outputs files
"jpg" => "image/jpeg",
"jpeg" => "image/jpeg",
"gif" => "image/gif",
"png" => "image/png",
"webp" => "image/webp",
"heic" => "image/heic",
"avif" => "image/avif",
];
+18
View File
@@ -268,6 +268,24 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_COMPRESSION_ENABLED',
'description' => 'This option allows you to enable or disable the response compression for the Appwrite API. It\'s enabled by default with value "enabled", and to disable it, pass value "disabled".',
'introduction' => '1.6.0',
'default' => 'enabled',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_COMPRESSION_MIN_SIZE_BYTES',
'description' => 'This option allows you to set the minimum size in bytes for the response compression to be applied. The default value is 1024 bytes.',
'introduction' => '1.6.0',
'default' => 1024,
'required' => false,
'question' => '',
'filter' => ''
]
],
],
[
File diff suppressed because it is too large Load Diff
+118 -64
View File
@@ -1,6 +1,11 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\URL\URL as URLParse;
use Appwrite\Utopia\Response;
use chillerlan\QRCode\QRCode;
@@ -61,9 +66,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
unset($image);
};
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) {
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger) {
try {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
@@ -122,7 +127,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
do {
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
$gitHubSession = new Document();
@@ -164,13 +169,20 @@ App::get('/v1/avatars/credit-cards/:code')
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resource', 'avatar/credit-card')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getCreditCard')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-credit-card.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->label('sdk', new Method(
namespace: 'avatars',
name: 'getCreditCard',
description: '/docs/references/avatars/get-credit-card.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::IMAGE_PNG
))
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
@@ -184,13 +196,20 @@ App::get('/v1/avatars/browsers/:code')
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resource', 'avatar/browser')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getBrowser')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-browser.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->label('sdk', new Method(
namespace: 'avatars',
name: 'getBrowser',
description: '/docs/references/avatars/get-browser.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::IMAGE_PNG
))
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
@@ -204,13 +223,20 @@ App::get('/v1/avatars/flags/:code')
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resource', 'avatar/flag')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getFlag')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-flag.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->label('sdk', new Method(
namespace: 'avatars',
name: 'getFlag',
description: '/docs/references/avatars/get-flag.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::IMAGE_PNG
))
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
@@ -224,13 +250,20 @@ App::get('/v1/avatars/image')
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resource', 'avatar/image')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getImage')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-image.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->label('sdk', new Method(
namespace: 'avatars',
name: 'getImage',
description: '/docs/references/avatars/get-image.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::IMAGE
))
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000. Defaults to 400.', true)
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000. Defaults to 400.', true)
@@ -287,13 +320,20 @@ App::get('/v1/avatars/favicon')
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resource', 'avatar/favicon')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getFavicon')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-favicon.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->label('sdk', new Method(
namespace: 'avatars',
name: 'getFavicon',
description: '/docs/references/avatars/get-favicon.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::IMAGE
))
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
->inject('response')
->action(function (string $url, Response $response) {
@@ -430,13 +470,20 @@ App::get('/v1/avatars/qr')
->desc('Get QR code')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getQR')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-qr.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->label('sdk', new Method(
namespace: 'avatars',
name: 'getQR',
description: '/docs/references/avatars/get-qr.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::IMAGE_PNG
))
->param('text', '', new Text(512), 'Plain text to be converted to QR code image.')
->param('size', 400, new Range(1, 1000), 'QR code size. Pass an integer between 1 to 1000. Defaults to 400.', true)
->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true)
@@ -471,13 +518,20 @@ App::get('/v1/avatars/initials')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache.resource', 'avatar/initials')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getInitials')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-initials.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE_PNG)
->label('sdk', new Method(
namespace: 'avatars',
name: 'getInitials',
description: '/docs/references/avatars/get-initials.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::IMAGE_PNG
))
->param('name', '', new Text(128), 'Full Name. When empty, current user name or email will be used. Max length: 128 chars.', true)
->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
@@ -565,14 +619,14 @@ App::get('/v1/cards/cloud')
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('response')
->inject('heroes')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -583,7 +637,7 @@ App::get('/v1/cards/cloud')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
@@ -772,14 +826,14 @@ App::get('/v1/cards/cloud-back')
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('response')
->inject('heroes')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -789,7 +843,7 @@ App::get('/v1/cards/cloud-back')
$userId = $user->getId();
$email = $user->getAttribute('email', '');
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
$githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes);
@@ -850,14 +904,14 @@ App::get('/v1/cards/cloud-og')
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('response')
->inject('heroes')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -872,7 +926,7 @@ App::get('/v1/cards/cloud-og')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
+31 -14
View File
@@ -1,6 +1,10 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Document;
@@ -21,13 +25,19 @@ App::get('/v1/console/variables')
->desc('Get variables')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'console')
->label('sdk.method', 'variables')
->label('sdk.description', '/docs/references/console/variables.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_CONSOLE_VARIABLES)
->label('sdk', new Method(
namespace: 'console',
name: 'variables',
description: '/docs/references/console/variables.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_CONSOLE_VARIABLES,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
$isDomainEnabled = !empty(System::getEnv('_APP_DOMAIN', ''))
@@ -60,18 +70,25 @@ App::post('/v1/console/assistant')
->desc('Ask query')
->groups(['api', 'assistant'])
->label('scope', 'assistant.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'assistant')
->label('sdk.method', 'chat')
->label('sdk.description', '/docs/references/assistant/chat.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_TEXT)
->label('sdk', new Method(
namespace: 'assistant',
name: 'chat',
description: '/docs/references/assistant/chat.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::TEXT
))
->label('abuse-limit', 15)
->label('abuse-key', 'userId:{userId}')
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
->inject('response')
->action(function (string $prompt, Response $response) {
$ch = curl_init('http://appwrite-assistant:3003/');
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
$responseHeaders = [];
$query = json_encode(['prompt' => $prompt]);
$headers = ['accept: text/event-stream'];
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+49 -29
View File
@@ -5,6 +5,10 @@ use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\GraphQL\Promises\Adapter;
use Appwrite\GraphQL\Schema;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use GraphQL\Error\DebugFlag;
@@ -38,13 +42,19 @@ App::get('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'graphql')
->label('sdk.hide', true)
->label('sdk.description', '/docs/references/graphql/get.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk', new Method(
namespace: 'graphql',
name: 'get',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
hide: true,
description: '/docs/references/graphql/get.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_ANY,
)
]
))
->label('abuse-limit', 60)
->label('abuse-time', 60)
->param('query', '', new Text(0, 0), 'The query to execute.')
@@ -78,17 +88,22 @@ App::post('/v1/graphql/mutation')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'graphql')
->label('sdk.method', 'mutation')
->label('sdk.methodType', 'graphql')
->label('sdk.description', '/docs/references/graphql/post.md')
->label('sdk.parameters', [
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
])
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk', new Method(
namespace: 'graphql',
name: 'mutation',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
description: '/docs/references/graphql/post.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_ANY,
)
],
type: MethodType::GRAPHQL,
additionalParameters: [
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
],
))
->label('abuse-limit', 60)
->label('abuse-time', 60)
->inject('request')
@@ -123,17 +138,22 @@ App::post('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'graphql')
->label('sdk.method', 'query')
->label('sdk.methodType', 'graphql')
->label('sdk.description', '/docs/references/graphql/post.md')
->label('sdk.parameters', [
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
])
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ANY)
->label('sdk', new Method(
namespace: 'graphql',
name: 'query',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
description: '/docs/references/graphql/post.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_ANY,
)
],
type: MethodType::GRAPHQL,
additionalParameters: [
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
],
))
->label('abuse-limit', 60)
->label('abuse-time', 60)
->inject('request')
+371 -273
View File
@@ -3,14 +3,18 @@
use Appwrite\ClamAV\Network;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\Pools\Group;
use Utopia\Queue\Client;
use Utopia\Queue\Connection;
use Utopia\Queue\Publisher;
use Utopia\Queue\Queue;
use Utopia\Registry\Registry;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
@@ -26,13 +30,19 @@ App::get('/v1/health')
->desc('Get HTTP')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/health/get.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->label('sdk', new Method(
namespace: 'health',
name: 'get',
auth: [AuthType::KEY],
description: '/docs/references/health/get.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_STATUS,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
@@ -49,9 +59,6 @@ App::get('/v1/health/version')
->desc('Get version')
->groups(['api', 'health'])
->label('scope', 'public')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_VERSION)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION);
@@ -61,13 +68,19 @@ App::get('/v1/health/db')
->desc('Get DB')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getDB')
->label('sdk.description', '/docs/references/health/get-db.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getDB',
description: '/docs/references/health/get-db.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_STATUS,
)
],
contentType: ContentType::JSON
))
->inject('response')
->inject('pools')
->action(function (Response $response, Group $pools) {
@@ -115,13 +128,19 @@ App::get('/v1/health/cache')
->desc('Get cache')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getCache')
->label('sdk.description', '/docs/references/health/get-cache.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getCache',
description: '/docs/references/health/get-cache.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_STATUS,
)
],
contentType: ContentType::JSON
))
->inject('response')
->inject('pools')
->action(function (Response $response, Group $pools) {
@@ -135,63 +154,7 @@ App::get('/v1/health/cache')
foreach ($configs as $key => $config) {
foreach ($config as $database) {
try {
$adapter = $pools->get($database)->pop()->getResource();
$checkStart = \microtime(true);
if ($adapter->ping()) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
} else {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
}
} catch (\Throwable $th) {
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
}
}
}
$response->dynamic(new Document([
'statuses' => $output,
'total' => count($output),
]), Response::MODEL_HEALTH_STATUS_LIST);
});
App::get('/v1/health/queue')
->desc('Get queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueue')
->label('sdk.description', '/docs/references/health/get-queue.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->inject('pools')
->action(function (Response $response, Group $pools) {
$output = [];
$configs = [
'Queue' => Config::getParam('pools-queue'),
];
foreach ($configs as $key => $config) {
foreach ($config as $database) {
try {
/** @var \Utopia\Cache\Adapter $adapter */
$adapter = $pools->get($database)->pop()->getResource();
$checkStart = \microtime(true);
@@ -229,13 +192,19 @@ App::get('/v1/health/pubsub')
->desc('Get pubsub')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getPubSub')
->label('sdk.description', '/docs/references/health/get-pubsub.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getPubSub',
description: '/docs/references/health/get-pubsub.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_STATUS,
)
],
contentType: ContentType::JSON
))
->inject('response')
->inject('pools')
->action(function (Response $response, Group $pools) {
@@ -249,6 +218,7 @@ App::get('/v1/health/pubsub')
foreach ($configs as $key => $config) {
foreach ($config as $database) {
try {
/** @var \Appwrite\PubSub\Adapter $adapter */
$adapter = $pools->get($database)->pop()->getResource();
$checkStart = \microtime(true);
@@ -286,13 +256,19 @@ App::get('/v1/health/time')
->desc('Get time')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getTime')
->label('sdk.description', '/docs/references/health/get-time.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_TIME)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getTime',
description: '/docs/references/health/get-time.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_TIME,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
@@ -343,21 +319,26 @@ App::get('/v1/health/queue/webhooks')
->desc('Get webhooks queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueWebhooks')
->label('sdk.description', '/docs/references/health/get-queue-webhooks.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueWebhooks',
description: '/docs/references/health/get-queue-webhooks.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::WEBHOOK_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::WEBHOOK_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -370,21 +351,26 @@ App::get('/v1/health/queue/logs')
->desc('Get logs queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueLogs')
->label('sdk.description', '/docs/references/health/get-queue-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueLogs',
description: '/docs/references/health/get-queue-logs.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::AUDITS_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::AUDITS_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -397,13 +383,19 @@ App::get('/v1/health/certificate')
->desc('Get the SSL certificate for a domain')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getCertificate')
->label('sdk.description', '/docs/references/health/get-certificate.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_CERTIFICATE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getCertificate',
description: '/docs/references/health/get-certificate.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_CERTIFICATE,
)
],
contentType: ContentType::JSON
))
->param('domain', null, new Multiple([new Domain(), new PublicDomain()]), Multiple::TYPE_STRING, 'Domain name')
->inject('response')
->action(function (string $domain, Response $response) {
@@ -447,21 +439,26 @@ App::get('/v1/health/queue/certificates')
->desc('Get certificates queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueCertificates')
->label('sdk.description', '/docs/references/health/get-queue-certificates.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueCertificates',
description: '/docs/references/health/get-queue-certificates.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::CERTIFICATES_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::CERTIFICATES_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -474,21 +471,26 @@ App::get('/v1/health/queue/builds')
->desc('Get builds queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueBuilds')
->label('sdk.description', '/docs/references/health/get-queue-builds.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueBuilds',
description: '/docs/references/health/get-queue-builds.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::BUILDS_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::BUILDS_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -501,22 +503,27 @@ App::get('/v1/health/queue/databases')
->desc('Get databases queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueDatabases')
->label('sdk.description', '/docs/references/health/get-queue-databases.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueDatabases',
description: '/docs/references/health/get-queue-databases.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('name', 'database_db_main', new Text(256), 'Queue name for which to check the queue size', true)
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (string $name, int|string $threshold, Connection $queue, Response $response) {
->action(function (string $name, int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client($name, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue($name));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -529,21 +536,26 @@ App::get('/v1/health/queue/deletes')
->desc('Get deletes queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueDeletes')
->label('sdk.description', '/docs/references/health/get-queue-deletes.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueDeletes',
description: '/docs/references/health/get-queue-deletes.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::DELETE_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::DELETE_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -556,21 +568,26 @@ App::get('/v1/health/queue/mails')
->desc('Get mails queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueMails')
->label('sdk.description', '/docs/references/health/get-queue-mails.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueMails',
description: '/docs/references/health/get-queue-mails.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MAILS_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::MAILS_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -583,21 +600,26 @@ App::get('/v1/health/queue/messaging')
->desc('Get messaging queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueMessaging')
->label('sdk.description', '/docs/references/health/get-queue-messaging.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueMessaging',
description: '/docs/references/health/get-queue-messaging.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MESSAGING_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::MESSAGING_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -610,21 +632,26 @@ App::get('/v1/health/queue/migrations')
->desc('Get migrations queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueMigrations')
->label('sdk.description', '/docs/references/health/get-queue-migrations.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueMigrations',
description: '/docs/references/health/get-queue-migrations.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::MIGRATIONS_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::MIGRATIONS_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -637,21 +664,26 @@ App::get('/v1/health/queue/functions')
->desc('Get functions queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueFunctions')
->label('sdk.description', '/docs/references/health/get-queue-functions.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueFunctions',
description: '/docs/references/health/get-queue-functions.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::FUNCTIONS_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::FUNCTIONS_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -660,25 +692,30 @@ App::get('/v1/health/queue/functions')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
App::get('/v1/health/queue/usage')
->desc('Get usage queue')
App::get('/v1/health/queue/stats-resources')
->desc('Get stats resources queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueUsage')
->label('sdk.description', '/docs/references/health/get-queue-usage.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueStatsResources',
description: '/docs/references/health/get-queue-stats-resources.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::USAGE_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::STATS_RESOURCES_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -687,25 +724,62 @@ App::get('/v1/health/queue/usage')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
App::get('/v1/health/queue/usage-dump')
App::get('/v1/health/queue/stats-usage')
->desc('Get stats usage queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueUsage',
description: '/docs/references/health/get-queue-stats-usage.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$size = $publisher->getQueueSize(new Queue(Event::STATS_USAGE_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
}
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
App::get('/v1/health/queue/stats-usage-dump')
->desc('Get usage dump queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueUsageDump')
->label('sdk.description', '/docs/references/health/get-queue-usage-dump.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getQueueStatsUsageDump',
description: '/docs/references/health/get-queue-stats-usage-dump.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('queue')
->inject('publisher')
->inject('response')
->action(function (int|string $threshold, Connection $queue, Response $response) {
->action(function (int|string $threshold, Publisher $publisher, Response $response) {
$threshold = \intval($threshold);
$client = new Client(Event::USAGE_DUMP_QUEUE_NAME, $queue);
$size = $client->getQueueSize();
$size = $publisher->getQueueSize(new Queue(Event::STATS_USAGE_DUMP_QUEUE_NAME));
if ($size >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
@@ -718,13 +792,19 @@ App::get('/v1/health/storage/local')
->desc('Get local storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getStorageLocal')
->label('sdk.description', '/docs/references/health/get-storage-local.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getStorageLocal',
description: '/docs/references/health/get-storage-local.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_STATUS,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
@@ -761,13 +841,19 @@ App::get('/v1/health/storage')
->desc('Get storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getStorage')
->label('sdk.description', '/docs/references/health/get-storage.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getStorage',
description: '/docs/references/health/get-storage.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_STATUS,
)
],
contentType: ContentType::JSON
))
->inject('response')
->inject('deviceForFiles')
->inject('deviceForFunctions')
@@ -802,13 +888,19 @@ App::get('/v1/health/anti-virus')
->desc('Get antivirus')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getAntivirus')
->label('sdk.description', '/docs/references/health/get-storage-anti-virus.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_ANTIVIRUS)
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getAntivirus',
description: '/docs/references/health/get-storage-anti-virus.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_ANTIVIRUS,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
@@ -841,17 +933,28 @@ App::get('/v1/health/queue/failed/:name')
->desc('Get number of failed queue jobs')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'health')
->label('sdk.method', 'getFailedJobs')
->label('sdk', new Method(
auth: [AuthType::KEY],
namespace: 'health',
name: 'getFailedJobs',
description: '/docs/references/health/get-failed-queue-jobs.md',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('name', '', new WhiteList([
Event::DATABASE_QUEUE_NAME,
Event::DELETE_QUEUE_NAME,
Event::AUDITS_QUEUE_NAME,
Event::MAILS_QUEUE_NAME,
Event::FUNCTIONS_QUEUE_NAME,
Event::USAGE_QUEUE_NAME,
Event::USAGE_DUMP_QUEUE_NAME,
Event::STATS_RESOURCES_QUEUE_NAME,
Event::STATS_USAGE_QUEUE_NAME,
Event::STATS_USAGE_DUMP_QUEUE_NAME,
Event::WEBHOOK_QUEUE_NAME,
Event::CERTIFICATES_QUEUE_NAME,
Event::BUILDS_QUEUE_NAME,
@@ -859,17 +962,12 @@ App::get('/v1/health/queue/failed/:name')
Event::MIGRATIONS_QUEUE_NAME
]), 'The name of the queue')
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->label('sdk.description', '/docs/references/health/get-failed-queue-jobs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
->inject('response')
->inject('queue')
->action(function (string $name, int|string $threshold, Response $response, Connection $queue) {
->inject('publisher')
->action(function (string $name, int|string $threshold, Response $response, Publisher $publisher) {
$threshold = \intval($threshold);
$client = new Client($name, $queue);
$failed = $client->countFailedJobs();
$failed = $publisher->getQueueSize(new Queue($name), failedJobs: true);
if ($failed >= $threshold) {
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue failed jobs threshold hit. Current size is {$failed} and threshold is {$threshold}.");
+99 -72
View File
@@ -1,5 +1,8 @@
<?php
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
@@ -12,15 +15,18 @@ App::get('/v1/locale')
->desc('Get user locale')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/locale/get-locale.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOCALE)
->label('sdk.offline.model', '/localed')
->label('sdk.offline.key', 'current')
->label('sdk', new Method(
namespace: 'locale',
name: 'get',
description: '/docs/references/locale/get-locale.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_LOCALE,
)
]
))
->inject('request')
->inject('response')
->inject('locale')
@@ -72,15 +78,18 @@ App::get('/v1/locale/codes')
->desc('List locale codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'listCodes')
->label('sdk.description', '/docs/references/locale/list-locale-codes.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOCALE_CODE_LIST)
->label('sdk.offline.model', '/locale/localeCode')
->label('sdk.offline.key', 'current')
->label('sdk', new Method(
namespace: 'locale',
name: 'listCodes',
description: '/docs/references/locale/list-locale-codes.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_LOCALE_CODE_LIST,
)
]
))
->inject('response')
->action(function (Response $response) {
$codes = Config::getParam('locale-codes');
@@ -94,15 +103,18 @@ App::get('/v1/locale/countries')
->desc('List countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'listCountries')
->label('sdk.description', '/docs/references/locale/list-countries.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
->label('sdk.offline.model', '/locale/countries')
->label('sdk.offline.response.key', 'code')
->label('sdk', new Method(
namespace: 'locale',
name: 'listCountries',
description: '/docs/references/locale/list-countries.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_COUNTRY_LIST,
)
]
))
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -127,15 +139,18 @@ App::get('/v1/locale/countries/eu')
->desc('List EU countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'listCountriesEU')
->label('sdk.description', '/docs/references/locale/list-countries-eu.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
->label('sdk.offline.model', '/locale/countries/eu')
->label('sdk.offline.response.key', 'code')
->label('sdk', new Method(
namespace: 'locale',
name: 'listCountriesEU',
description: '/docs/references/locale/list-countries-eu.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_COUNTRY_LIST,
)
]
))
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -162,15 +177,18 @@ App::get('/v1/locale/countries/phones')
->desc('List countries phone codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'listCountriesPhones')
->label('sdk.description', '/docs/references/locale/list-countries-phones.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PHONE_LIST)
->label('sdk.offline.model', '/locale/countries/phones')
->label('sdk.offline.response.key', 'countryCode')
->label('sdk', new Method(
namespace: 'locale',
name: 'listCountriesPhones',
description: '/docs/references/locale/list-countries-phones.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PHONE_LIST,
)
]
))
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -196,15 +214,18 @@ App::get('/v1/locale/continents')
->desc('List continents')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'listContinents')
->label('sdk.description', '/docs/references/locale/list-continents.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_CONTINENT_LIST)
->label('sdk.offline.model', '/locale/continents')
->label('sdk.offline.response.key', 'code')
->label('sdk', new Method(
namespace: 'locale',
name: 'listContinents',
description: '/docs/references/locale/list-continents.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_CONTINENT_LIST,
)
]
))
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -228,15 +249,18 @@ App::get('/v1/locale/currencies')
->desc('List currencies')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'listCurrencies')
->label('sdk.description', '/docs/references/locale/list-currencies.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_CURRENCY_LIST)
->label('sdk.offline.model', '/locale/currencies')
->label('sdk.offline.response.key', 'code')
->label('sdk', new Method(
namespace: 'locale',
name: 'listCurrencies',
description: '/docs/references/locale/list-currencies.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_CURRENCY_LIST,
)
]
))
->inject('response')
->action(function (Response $response) {
$list = Config::getParam('locale-currencies');
@@ -251,15 +275,18 @@ App::get('/v1/locale/languages')
->desc('List languages')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'locale')
->label('sdk.method', 'listLanguages')
->label('sdk.description', '/docs/references/locale/list-languages.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST)
->label('sdk.offline.model', '/locale/languages')
->label('sdk.offline.response.key', 'code')
->label('sdk', new Method(
namespace: 'locale',
name: 'listLanguages',
description: '/docs/references/locale/list-languages.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_LANGUAGE_LIST,
)
]
))
->inject('response')
->action(function (Response $response) {
$list = Config::getParam('locale-languages');
File diff suppressed because it is too large Load Diff
+150 -555
View File
@@ -1,17 +1,16 @@
<?php
use Appwrite\Auth\OAuth2\Firebase as OAuth2Firebase;
use Appwrite\Event\Event;
use Appwrite\Event\Migration;
use Appwrite\Extend\Exception;
use Appwrite\Permission;
use Appwrite\Role;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
@@ -22,9 +21,7 @@ use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Host;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
use Utopia\Validator\URL;
@@ -38,13 +35,18 @@ App::post('/v1/migrations/appwrite')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'createAppwriteMigration')
->label('sdk.description', '/docs/references/migrations/migration-appwrite.md')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION)
->label('sdk', new Method(
namespace: 'migrations',
name: 'createAppwriteMigration',
description: '/docs/references/migrations/migration-appwrite.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Appwrite::getSupportedResources())), 'List of resources to migrate')
->param('endpoint', '', new URL(), "Source's Appwrite Endpoint")
->param('projectId', '', new UID(), "Source's Project ID")
@@ -87,122 +89,25 @@ App::post('/v1/migrations/appwrite')
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::post('/v1/migrations/firebase/oauth')
->groups(['api', 'migrations'])
->desc('Migrate Firebase data (OAuth)')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'createFirebaseOAuthMigration')
->label('sdk.description', '/docs/references/migrations/migration-firebase.md')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION)
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('projectId', '', new Text(65536), 'Project ID of the Firebase Project')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->inject('request')
->action(function (array $resources, string $projectId, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations, Request $request) {
$firebase = new OAuth2Firebase(
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
);
$identity = $dbForConsole->findOne('identities', [
Query::equal('provider', ['firebase']),
Query::equal('userInternalId', [$user->getInternalId()]),
]);
if ($identity->isEmpty()) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$accessToken = $identity->getAttribute('providerAccessToken');
$refreshToken = $identity->getAttribute('providerRefreshToken');
$accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry');
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
if ($isExpired) {
$firebase->refreshTokens($refreshToken);
$accessToken = $firebase->getAccessToken('');
$refreshToken = $firebase->getRefreshToken('');
$verificationId = $firebase->getUserID($accessToken);
if (empty($verificationId)) {
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Another request is currently refreshing OAuth token. Please try again.');
}
$identity = $identity
->setAttribute('providerAccessToken', $accessToken)
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry('')));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
}
if ($identity->getAttribute('secrets')) {
$serviceAccount = $identity->getAttribute('secrets');
} else {
$firebase->cleanupServiceAccounts($accessToken, $projectId);
$serviceAccount = $firebase->createServiceAccount($accessToken, $projectId);
$identity = $identity
->setAttribute('secrets', json_encode($serviceAccount));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
}
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => json_encode($serviceAccount),
],
'resources' => $resources,
'statusCounters' => '{}',
'resourceData' => '{}',
'errors' => []
]));
$queueForEvents->setParam('migrationId', $migration->getId());
// Trigger Transfer
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setUser($user)
->trigger();
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
->desc('Migrate Firebase data (Service Account)')
->desc('Migrate Firebase data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'createFirebaseMigration')
->label('sdk.description', '/docs/references/migrations/migration-firebase.md')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION)
->label('sdk', new Method(
namespace: 'migrations',
name: 'createFirebaseMigration',
description: '/docs/references/migrations/migration-firebase.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
@@ -257,13 +162,18 @@ App::post('/v1/migrations/supabase')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'createSupabaseMigration')
->label('sdk.description', '/docs/references/migrations/migration-supabase.md')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION)
->label('sdk', new Method(
namespace: 'migrations',
name: 'createSupabaseMigration',
description: '/docs/references/migrations/migration-supabase.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate')
->param('endpoint', '', new URL(), 'Source\'s Supabase Endpoint')
->param('apiKey', '', new Text(512), 'Source\'s API Key')
@@ -318,13 +228,18 @@ App::post('/v1/migrations/nhost')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'createNHostMigration')
->label('sdk.description', '/docs/references/migrations/migration-nhost.md')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION)
->label('sdk', new Method(
namespace: 'migrations',
name: 'createNHostMigration',
description: '/docs/references/migrations/migration-nhost.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate')
->param('subdomain', '', new Text(512), 'Source\'s Subdomain')
->param('region', '', new Text(512), 'Source\'s Region')
@@ -379,13 +294,18 @@ App::get('/v1/migrations')
->groups(['api', 'migrations'])
->desc('List migrations')
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/migrations/list-migrations.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION_LIST)
->label('sdk', new Method(
namespace: 'migrations',
name: 'list',
description: '/docs/references/migrations/list-migrations.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_LIST,
)
]
))
->param('queries', [], new Migrations(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Migrations::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
@@ -438,13 +358,18 @@ App::get('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Get migration')
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/migrations/get-migration.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION)
->label('sdk', new Method(
namespace: 'migrations',
name: 'get',
description: '/docs/references/migrations/get-migration.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION,
)
]
))
->param('migrationId', '', new UID(), 'Migration unique ID.')
->inject('response')
->inject('dbForProject')
@@ -462,13 +387,18 @@ App::get('/v1/migrations/appwrite/report')
->groups(['api', 'migrations'])
->desc('Generate a report on Appwrite data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'getAppwriteReport')
->label('sdk.description', '/docs/references/migrations/migration-appwrite-report.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION_REPORT)
->label('sdk', new Method(
namespace: 'migrations',
name: 'getAppwriteReport',
description: '/docs/references/migrations/migration-appwrite-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Appwrite::getSupportedResources())), 'List of resources to migrate')
->param('endpoint', '', new URL(), "Source's Appwrite Endpoint")
->param('projectID', '', new Text(512), "Source's Project ID")
@@ -504,13 +434,18 @@ App::get('/v1/migrations/firebase/report')
->groups(['api', 'migrations'])
->desc('Generate a report on Firebase data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'getFirebaseReport')
->label('sdk.description', '/docs/references/migrations/migration-firebase-report.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION_REPORT)
->label('sdk', new Method(
namespace: 'migrations',
name: 'getFirebaseReport',
description: '/docs/references/migrations/migration-firebase-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
@@ -547,379 +482,22 @@ App::get('/v1/migrations/firebase/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/firebase/report/oauth')
->groups(['api', 'migrations'])
->desc('Generate a report on Firebase data using OAuth')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'getFirebaseReportOAuth')
->label('sdk.description', '/docs/references/migrations/migration-firebase-report.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION_REPORT)
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('projectId', '', new Text(65536), 'Project ID')
->inject('response')
->inject('request')
->inject('user')
->inject('dbForConsole')
->action(function (array $resources, string $projectId, Response $response, Request $request, Document $user, Database $dbForConsole) {
$firebase = new OAuth2Firebase(
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
);
$identity = $dbForConsole->findOne('identities', [
Query::equal('provider', ['firebase']),
Query::equal('userInternalId', [$user->getInternalId()]),
]);
if ($identity->isEmpty()) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$accessToken = $identity->getAttribute('providerAccessToken');
$refreshToken = $identity->getAttribute('providerRefreshToken');
$accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry');
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
if (System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', '') === '' || System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', '') === '') {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
if ($isExpired) {
$firebase->refreshTokens($refreshToken);
$accessToken = $firebase->getAccessToken('');
$refreshToken = $firebase->getRefreshToken('');
$verificationId = $firebase->getUserID($accessToken);
if (empty($verificationId)) {
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Another request is currently refreshing OAuth token. Please try again.');
}
$identity = $identity
->setAttribute('providerAccessToken', $accessToken)
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry('')));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
}
// Get Service Account
if ($identity->getAttribute('secrets')) {
$serviceAccount = $identity->getAttribute('secrets');
} else {
$firebase->cleanupServiceAccounts($accessToken, $projectId);
$serviceAccount = $firebase->createServiceAccount($accessToken, $projectId);
$identity = $identity
->setAttribute('secrets', json_encode($serviceAccount));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
}
$firebase = new Firebase($serviceAccount);
try {
$report = $firebase->report($resources);
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/firebase/connect')
->desc('Authorize with Firebase')
->groups(['api', 'migrations'])
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'createFirebaseAuth')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.methodType', 'webAuth')
->label('sdk.hide', true)
->param('redirect', '', fn ($clients) => new Host($clients), 'URL to redirect back to your Firebase authorization. Only console hostnames are allowed.', true, ['clients'])
->param('projectId', '', new UID(), 'Project ID')
->inject('response')
->inject('request')
->inject('user')
->inject('dbForConsole')
->action(function (string $redirect, string $projectId, Response $response, Request $request, Document $user, Database $dbForConsole) {
$state = \json_encode([
'projectId' => $projectId,
'redirect' => $redirect,
]);
$prefs = $user->getAttribute('prefs', []);
$prefs['migrationState'] = $state;
$user->setAttribute('prefs', $prefs);
$dbForConsole->updateDocument('users', $user->getId(), $user);
$oauth2 = new OAuth2Firebase(
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
);
$url = $oauth2->getLoginURL();
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($url);
});
App::get('/v1/migrations/firebase/redirect')
->desc('Capture and receive data on Firebase authorization')
->groups(['api', 'migrations'])
->label('scope', 'public')
->label('error', __DIR__ . '/../../views/general/error.phtml')
->param('code', '', new Text(2048), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
->inject('user')
->inject('project')
->inject('request')
->inject('response')
->inject('dbForConsole')
->action(function (string $code, Document $user, Document $project, Request $request, Response $response, Database $dbForConsole) {
$state = $user['prefs']['migrationState'] ?? '{}';
$prefs['migrationState'] = '';
$user->setAttribute('prefs', $prefs);
$dbForConsole->updateDocument('users', $user->getId(), $user);
if (empty($state)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Installation requests from organisation members for the Appwrite Google App are currently unsupported.');
}
$state = \json_decode($state, true);
$redirect = $state['redirect'] ?? '';
$projectId = $state['projectId'] ?? '';
$project = $dbForConsole->getDocument('projects', $projectId);
if (empty($redirect)) {
$redirect = $request->getProtocol() . '://' . $request->getHostname() . '/console/project-$projectId/settings/migrations';
}
if ($project->isEmpty()) {
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($redirect);
return;
}
// OAuth Authroization
if (!empty($code)) {
$oauth2 = new OAuth2Firebase(
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
);
$accessToken = $oauth2->getAccessToken($code);
$refreshToken = $oauth2->getRefreshToken($code);
$accessTokenExpiry = $oauth2->getAccessTokenExpiry($code);
$email = $oauth2->getUserEmail($accessToken);
$oauth2ID = $oauth2->getUserID($accessToken);
if (empty($accessToken)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to get access token.');
}
if (empty($refreshToken)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to get refresh token.');
}
if (empty($accessTokenExpiry)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to get access token expiry.');
}
// Makes sure this email is not already used in another identity
$identity = $dbForConsole->findOne('identities', [
Query::equal('providerEmail', [$email]),
]);
if (!$identity->isEmpty()) {
if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
}
if (!$identity->isEmpty()) {
$identity = $identity
->setAttribute('providerAccessToken', $accessToken)
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
} else {
$identity = $dbForConsole->createDocument('identities', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::user($user->getId())),
Permission::delete(Role::user($user->getId())),
],
'userInternalId' => $user->getInternalId(),
'userId' => $user->getId(),
'provider' => 'firebase',
'providerUid' => $oauth2ID,
'providerEmail' => $email,
'providerAccessToken' => $accessToken,
'providerRefreshToken' => $refreshToken,
'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry),
]));
}
} else {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Missing OAuth2 code.');
}
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($redirect);
});
App::get('/v1/migrations/firebase/projects')
->desc('List Firebase projects')
->groups(['api', 'migrations'])
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'listFirebaseProjects')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST)
->inject('user')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('request')
->action(function (Document $user, Response $response, Document $project, Database $dbForConsole, Request $request) {
$firebase = new OAuth2Firebase(
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
);
$identity = $dbForConsole->findOne('identities', [
Query::equal('provider', ['firebase']),
Query::equal('userInternalId', [$user->getInternalId()]),
]);
if ($identity->isEmpty()) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$accessToken = $identity->getAttribute('providerAccessToken');
$refreshToken = $identity->getAttribute('providerRefreshToken');
$accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry');
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
if (System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', '') === '' || System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', '') === '') {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
try {
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
if ($isExpired) {
try {
$firebase->refreshTokens($refreshToken);
} catch (\Throwable $e) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$accessToken = $firebase->getAccessToken('');
$refreshToken = $firebase->getRefreshToken('');
$verificationId = $firebase->getUserID($accessToken);
if (empty($verificationId)) {
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Another request is currently refreshing OAuth token. Please try again.');
}
$identity = $identity
->setAttribute('providerAccessToken', $accessToken)
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry('')));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
}
$projects = $firebase->getProjects($accessToken);
$output = [];
foreach ($projects as $project) {
$output[] = [
'displayName' => $project['displayName'],
'projectId' => $project['projectId'],
];
}
} catch (\Throwable $e) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$response->dynamic(new Document([
'projects' => $output,
'total' => count($output),
]), Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST);
});
App::get('/v1/migrations/firebase/deauthorize')
->desc('Revoke Appwrite\'s authorization to access Firebase projects')
->groups(['api', 'migrations'])
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'deleteFirebaseAuth')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->inject('user')
->inject('response')
->inject('dbForConsole')
->action(function (Document $user, Response $response, Database $dbForConsole) {
$identity = $dbForConsole->findOne('identities', [
Query::equal('provider', ['firebase']),
Query::equal('userInternalId', [$user->getInternalId()]),
]);
if ($identity->isEmpty()) {
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Not authenticated with Firebase'); //TODO: Replace with USER_IDENTITY_NOT_FOUND
}
$dbForConsole->deleteDocument('identities', $identity->getId());
$response->noContent();
});
App::get('/v1/migrations/supabase/report')
->groups(['api', 'migrations'])
->desc('Generate a report on Supabase Data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'getSupabaseReport')
->label('sdk.description', '/docs/references/migrations/migration-supabase-report.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION_REPORT)
->label('sdk', new Method(
namespace: 'migrations',
name: 'getSupabaseReport',
description: '/docs/references/migrations/migration-supabase-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate')
->param('endpoint', '', new URL(), 'Source\'s Supabase Endpoint.')
->param('apiKey', '', new Text(512), 'Source\'s API Key.')
@@ -956,13 +534,18 @@ App::get('/v1/migrations/nhost/report')
->groups(['api', 'migrations'])
->desc('Generate a report on NHost Data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'getNHostReport')
->label('sdk.description', '/docs/references/migrations/migration-nhost-report.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION_REPORT)
->label('sdk', new Method(
namespace: 'migrations',
name: 'getNHostReport',
description: '/docs/references/migrations/migration-nhost-report.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MIGRATION_REPORT,
)
]
))
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate.')
->param('subdomain', '', new Text(512), 'Source\'s Subdomain.')
->param('region', '', new Text(512), 'Source\'s Region.')
@@ -1002,13 +585,18 @@ App::patch('/v1/migrations/:migrationId')
->label('event', 'migrations.[migrationId].retry')
->label('audits.event', 'migration.retry')
->label('audits.resource', 'migrations/{request.migrationId}')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'retry')
->label('sdk.description', '/docs/references/migrations/retry-migration.md')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MIGRATION)
->label('sdk', new Method(
namespace: 'migrations',
name: 'retry',
description: '/docs/references/migrations/retry-migration.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_ACCEPTED,
model: Response::MODEL_MIGRATION,
)
]
))
->param('migrationId', '', new UID(), 'Migration unique ID.')
->inject('response')
->inject('dbForProject')
@@ -1047,12 +635,19 @@ App::delete('/v1/migrations/:migrationId')
->label('event', 'migrations.[migrationId].delete')
->label('audits.event', 'migrationId.delete')
->label('audits.resource', 'migrations/{request.migrationId}')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/migrations/delete-migration.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'migrations',
name: 'delete',
description: '/docs/references/migrations/delete-migration.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('migrationId', '', new UID(), 'Migration ID.')
->inject('response')
->inject('dbForProject')
+136 -49
View File
@@ -1,6 +1,10 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
@@ -20,18 +24,25 @@ App::get('/v1/project/usage')
->desc('Get project usage stats')
->groups(['api', 'usage'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_PROJECT)
->label('sdk', new Method(
namespace: 'project',
name: 'getUsage',
description: '/docs/references/project/get-usage.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_USAGE_PROJECT,
)
]
))
->param('startDate', '', new DateTimeValidator(), 'Starting date for the usage')
->param('endDate', '', new DateTimeValidator(), 'End date for the usage')
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
->inject('response')
->inject('dbForProject')
->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject) {
->inject('smsRates')
->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject, array $smsRates) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
@@ -49,7 +60,9 @@ App::get('/v1/project/usage')
METRIC_FILES_STORAGE,
METRIC_DATABASES_STORAGE,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS_STORAGE
METRIC_BUILDS_STORAGE,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_WRITES,
],
'period' => [
METRIC_NETWORK_REQUESTS,
@@ -59,7 +72,9 @@ App::get('/v1/project/usage')
METRIC_EXECUTIONS,
METRIC_DATABASES_STORAGE,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS
METRIC_BUILDS_MB_SECONDS,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_WRITES,
]
];
@@ -257,6 +272,46 @@ App::get('/v1/project/usage')
];
}, $dbForProject->find('functions'));
// This total is includes free and paid SMS usage
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay),
Query::lessThan('time', $lastDay),
]));
// This estimate is only for paid SMS usage
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay),
Query::lessThan('time', $lastDay),
]));
$authPhoneEstimate = 0.0;
$authPhoneCountryBreakdown = [];
foreach ($authPhoneMetrics as $metric) {
$parts = explode('.', $metric->getAttribute('metric'));
$countryCode = $parts[3] ?? null;
if ($countryCode === null) {
continue;
}
$value = $metric->getAttribute('value', 0);
if (isset($smsRates[$countryCode])) {
$authPhoneEstimate += $value * $smsRates[$countryCode];
}
$authPhoneCountryBreakdown[] = [
'name' => $countryCode,
'value' => $value,
'estimate' => isset($smsRates[$countryCode])
? $value * $smsRates[$countryCode]
: 0.0,
];
}
// merge network inbound + outbound
$projectBandwidth = [];
foreach ($usage[METRIC_NETWORK_INBOUND] as $item) {
@@ -295,14 +350,19 @@ App::get('/v1/project/usage')
'functionsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE] + $total[METRIC_BUILDS_STORAGE],
'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE],
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
'databasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS],
'databasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES],
'executionsBreakdown' => $executionsBreakdown,
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown,
'databasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS],
'databasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES],
'databasesStorageBreakdown' => $databasesStorageBreakdown,
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
'functionsStorageBreakdown' => $functionsStorageBreakdown,
'authPhoneTotal' => $authPhoneTotal,
'authPhoneEstimate' => $authPhoneEstimate,
'authPhoneCountryBreakdown' => $authPhoneCountryBreakdown,
]), Response::MODEL_USAGE_PROJECT);
});
@@ -313,20 +373,25 @@ App::post('/v1/project/variables')
->groups(['api'])
->label('scope', 'projects.write')
->label('audits.event', 'variable.create')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
->label('sdk.method', 'createVariable')
->label('sdk.description', '/docs/references/project/create-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->label('sdk', new Method(
namespace: 'project',
name: 'createVariable',
description: '/docs/references/project/create-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_VARIABLE,
)
]
))
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
$variableId = ID::unique();
$variable = new Document([
@@ -367,13 +432,18 @@ App::get('/v1/project/variables')
->desc('List variables')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
->label('sdk.method', 'listVariables')
->label('sdk.description', '/docs/references/project/list-variables.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE_LIST)
->label('sdk', new Method(
namespace: 'project',
name: 'listVariables',
description: '/docs/references/project/list-variables.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VARIABLE_LIST,
)
]
))
->inject('response')
->inject('dbForProject')
->action(function (Response $response, Database $dbForProject) {
@@ -392,13 +462,18 @@ App::get('/v1/project/variables/:variableId')
->desc('Get variable')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
->label('sdk.method', 'getVariable')
->label('sdk.description', '/docs/references/project/get-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->label('sdk', new Method(
namespace: 'project',
name: 'getVariable',
description: '/docs/references/project/get-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VARIABLE,
)
]
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->inject('response')
->inject('project')
@@ -416,21 +491,26 @@ App::put('/v1/project/variables/:variableId')
->desc('Update variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
->label('sdk.method', 'updateVariable')
->label('sdk.description', '/docs/references/project/update-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->label('sdk', new Method(
namespace: 'project',
name: 'updateVariable',
description: '/docs/references/project/update-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VARIABLE,
)
]
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
@@ -462,12 +542,19 @@ App::delete('/v1/project/variables/:variableId')
->desc('Delete variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
->label('sdk.method', 'deleteVariable')
->label('sdk.description', '/docs/references/project/delete-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'project',
name: 'deleteVariable',
description: '/docs/references/project/delete-variable.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->inject('project')
->inject('response')
File diff suppressed because it is too large Load Diff
+99 -59
View File
@@ -5,6 +5,10 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\CNAME;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Rules;
use Appwrite\Utopia\Response;
use Utopia\App;
@@ -29,13 +33,18 @@ App::post('/v1/proxy/rules')
->label('event', 'rules.[ruleId].create')
->label('audits.event', 'rule.create')
->label('audits.resource', 'rule/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'createRule')
->label('sdk.description', '/docs/references/proxy/create-rule.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
->label('sdk', new Method(
namespace: 'proxy',
name: 'createRule',
description: '/docs/references/proxy/create-rule.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_PROXY_RULE,
)
]
))
->param('domain', null, new ValidatorDomain(), 'Domain name.')
->param('resourceType', null, new WhiteList(['api', 'function']), 'Action definition for the rule. Possible values are "api", "function"')
->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.', true)
@@ -43,9 +52,9 @@ App::post('/v1/proxy/rules')
->inject('project')
->inject('queueForCertificates')
->inject('queueForEvents')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('dbForProject')
->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForConsole, Database $dbForProject) {
->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) {
$mainDomain = System::getEnv('_APP_DOMAIN', '');
if ($domain === $mainDomain) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.');
@@ -60,9 +69,15 @@ App::post('/v1/proxy/rules')
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
}
$document = $dbForConsole->findOne('rules', [
Query::equal('domain', [$domain]),
]);
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$document = $dbForPlatform->getDocument('rules', md5($domain));
} else {
$document = $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]);
}
if (!$document->isEmpty()) {
if ($document->getAttribute('projectId') === $project->getId()) {
@@ -103,7 +118,9 @@ App::post('/v1/proxy/rules')
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
}
$ruleId = ID::unique();
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
$rule = new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
@@ -137,7 +154,7 @@ App::post('/v1/proxy/rules')
}
$rule->setAttribute('status', $status);
$rule = $dbForConsole->createDocument('rules', $rule);
$rule = $dbForPlatform->createDocument('rules', $rule);
$queueForEvents->setParam('ruleId', $rule->getId());
@@ -152,19 +169,24 @@ App::get('/v1/proxy/rules')
->groups(['api', 'proxy'])
->desc('List rules')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'listRules')
->label('sdk.description', '/docs/references/proxy/list-rules.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE_LIST)
->label('sdk', new Method(
namespace: 'proxy',
name: 'listRules',
description: '/docs/references/proxy/list-rules.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROXY_RULE_LIST,
)
]
))
->param('queries', [], new Rules(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Rules::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForPlatform) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -193,7 +215,7 @@ App::get('/v1/proxy/rules')
}
$ruleId = $cursor->getValue();
$cursorDocument = $dbForConsole->getDocument('rules', $ruleId);
$cursorDocument = $dbForPlatform->getDocument('rules', $ruleId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Rule '{$ruleId}' for the 'cursor' value not found.");
@@ -204,16 +226,16 @@ App::get('/v1/proxy/rules')
$filterQueries = Query::groupByType($queries)['filters'];
$rules = $dbForConsole->find('rules', $queries);
$rules = $dbForPlatform->find('rules', $queries);
foreach ($rules as $rule) {
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
}
$response->dynamic(new Document([
'rules' => $rules,
'total' => $dbForConsole->count('rules', $filterQueries, APP_LIMIT_COUNT),
'total' => $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_PROXY_RULE_LIST);
});
@@ -221,25 +243,30 @@ App::get('/v1/proxy/rules/:ruleId')
->groups(['api', 'proxy'])
->desc('Get rule')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'getRule')
->label('sdk.description', '/docs/references/proxy/get-rule.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
->label('sdk', new Method(
namespace: 'proxy',
name: 'getRule',
description: '/docs/references/proxy/get-rule.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROXY_RULE,
)
]
))
->param('ruleId', '', new UID(), 'Rule ID.')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole) {
$rule = $dbForConsole->getDocument('rules', $ruleId);
->inject('dbForPlatform')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
@@ -253,26 +280,33 @@ App::delete('/v1/proxy/rules/:ruleId')
->label('event', 'rules.[ruleId].delete')
->label('audits.event', 'rules.delete')
->label('audits.resource', 'rule/{request.ruleId}')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'deleteRule')
->label('sdk.description', '/docs/references/proxy/delete-rule.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'proxy',
name: 'deleteRule',
description: '/docs/references/proxy/delete-rule.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('ruleId', '', new UID(), 'Rule ID.')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('queueForDeletes')
->inject('queueForEvents')
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes, Event $queueForEvents) {
$rule = $dbForConsole->getDocument('rules', $ruleId);
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes, Event $queueForEvents) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$dbForConsole->deleteDocument('rules', $rule->getId());
$dbForPlatform->deleteDocument('rules', $rule->getId());
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
@@ -290,21 +324,27 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
->label('event', 'rules.[ruleId].update')
->label('audits.event', 'rule.update')
->label('audits.resource', 'rule/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'updateRuleVerification')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
->label('sdk', new Method(
namespace: 'proxy',
name: 'updateRuleVerification',
description: '/docs/references/proxy/update-rule-verification.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROXY_RULE,
)
]
))
->param('ruleId', '', new UID(), 'Rule ID.')
->inject('response')
->inject('queueForCertificates')
->inject('queueForEvents')
->inject('project')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('log')
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole, Log $log) {
$rule = $dbForConsole->getDocument('rules', $ruleId);
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForPlatform, Log $log) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::RULE_NOT_FOUND);
@@ -334,7 +374,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
}
$dbForConsole->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
// Issue a TLS certificate when domain is verified
$queueForCertificates
@@ -345,7 +385,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
$queueForEvents->setParam('ruleId', $rule->getId());
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
+268 -147
View File
@@ -6,8 +6,14 @@ 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;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Buckets;
use Appwrite\Utopia\Database\Validator\Queries\Files;
@@ -15,8 +21,10 @@ use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\NotFound as NotFoundException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@@ -53,13 +61,18 @@ App::post('/v1/storage/buckets')
->label('event', 'buckets.[bucketId].create')
->label('audits.event', 'bucket.create')
->label('audits.resource', 'bucket/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createBucket')
->label('sdk.description', '/docs/references/storage/create-bucket.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET)
->label('sdk', new Method(
namespace: 'storage',
name: 'createBucket',
description: '/docs/references/storage/create-bucket.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_BUCKET,
)
]
))
->param('bucketId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Bucket name')
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
@@ -67,19 +80,20 @@ App::post('/v1/storage/buckets')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions);
$compression ??= Compression::NONE;
$encryption ??= true;
try {
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
if (empty($files)) {
@@ -131,7 +145,7 @@ App::post('/v1/storage/buckets')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity);
} catch (Duplicate) {
} catch (DuplicateException) {
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
}
@@ -149,13 +163,18 @@ App::get('/v1/storage/buckets')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listBuckets')
->label('sdk.description', '/docs/references/storage/list-buckets.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET_LIST)
->label('sdk', new Method(
namespace: 'storage',
name: 'listBuckets',
description: '/docs/references/storage/list-buckets.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_BUCKET_LIST,
)
]
))
->param('queries', [], new Buckets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Buckets::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
@@ -210,13 +229,18 @@ App::get('/v1/storage/buckets/:bucketId')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getBucket')
->label('sdk.description', '/docs/references/storage/get-bucket.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET)
->label('sdk', new Method(
namespace: 'storage',
name: 'getBucket',
description: '/docs/references/storage/get-bucket.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_BUCKET,
)
]
))
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->inject('response')
->inject('dbForProject')
@@ -239,13 +263,18 @@ App::put('/v1/storage/buckets/:bucketId')
->label('event', 'buckets.[bucketId].update')
->label('audits.event', 'bucket.update')
->label('audits.resource', 'bucket/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'updateBucket')
->label('sdk.description', '/docs/references/storage/update-bucket.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUCKET)
->label('sdk', new Method(
namespace: 'storage',
name: 'updateBucket',
description: '/docs/references/storage/update-bucket.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_BUCKET,
)
]
))
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->param('name', null, new Text(128), 'Bucket name', false)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
@@ -253,13 +282,13 @@ App::put('/v1/storage/buckets/:bucketId')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
@@ -272,11 +301,8 @@ App::put('/v1/storage/buckets/:bucketId')
$enabled ??= $bucket->getAttribute('enabled', true);
$encryption ??= $bucket->getAttribute('encryption', true);
$antivirus ??= $bucket->getAttribute('antivirus', true);
$compression ??= $bucket->getAttribute('compression', Compression::NONE);
/**
* Map aggregate permissions into the multiple permissions they represent,
* accounting for the resource type given that some types not allowed specific permissions.
*/
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions);
@@ -290,11 +316,11 @@ App::put('/v1/storage/buckets/:bucketId')
->setAttribute('encryption', $encryption)
->setAttribute('compression', $compression)
->setAttribute('antivirus', $antivirus));
$dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity);
$queueForEvents
->setParam('bucketId', $bucket->getId())
;
->setParam('bucketId', $bucket->getId());
$response->dynamic($bucket, Response::MODEL_BUCKET);
});
@@ -307,12 +333,19 @@ App::delete('/v1/storage/buckets/:bucketId')
->label('audits.event', 'bucket.delete')
->label('event', 'buckets.[bucketId].delete')
->label('audits.resource', 'bucket/{request.bucketId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteBucket')
->label('sdk.description', '/docs/references/storage/delete-bucket.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'storage',
name: 'deleteBucket',
description: '/docs/references/storage/delete-bucket.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('bucketId', '', new UID(), 'Bucket unique ID.')
->inject('response')
->inject('dbForProject')
@@ -342,7 +375,7 @@ App::delete('/v1/storage/buckets/:bucketId')
});
App::post('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
->alias('/v1/storage/files')
->desc('Create file')
->groups(['api', 'storage'])
->label('scope', 'files.write')
@@ -353,15 +386,20 @@ App::post('/v1/storage/buckets/:bucketId/files')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId},chunkId:{chunkId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createFile')
->label('sdk.description', '/docs/references/storage/create-file.md')
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.methodType', 'upload')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->label('sdk', new Method(
namespace: 'storage',
name: 'createFile',
description: '/docs/references/storage/create-file.md',
type: MethodType::UPLOAD,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
requestType: 'multipart/form-data',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_FILE,
)
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file).', skipValidation: true)
@@ -670,7 +708,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
'metadata' => $metadata,
]);
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
try {
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
} else {
$file = $file
->setAttribute('chunksUploaded', $chunksUploaded)
@@ -686,15 +728,19 @@ App::post('/v1/storage/buckets/:bucketId/files')
if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
try {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
}
}
$queueForEvents
->setParam('bucketId', $bucket->getId())
->setParam('fileId', $file->getId())
->setContext('bucket', $bucket)
;
->setContext('bucket', $bucket);
$metadata = null; // was causing leaks as it was passed by reference
@@ -704,18 +750,23 @@ App::post('/v1/storage/buckets/:bucketId/files')
});
App::get('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
->alias('/v1/storage/files')
->desc('List files')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listFiles')
->label('sdk.description', '/docs/references/storage/list-files.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE_LIST)
->label('sdk', new Method(
namespace: 'storage',
name: 'listFiles',
description: '/docs/references/storage/list-files.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_FILE_LIST,
)
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
@@ -739,11 +790,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries = Query::parseQueries($queries);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
@@ -781,12 +828,16 @@ App::get('/v1/storage/buckets/:bucketId/files')
$filterQueries = Query::groupByType($queries)['filters'];
if ($fileSecurity && !$valid) {
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
try {
if ($fileSecurity && !$valid) {
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$response->dynamic(new Document([
@@ -796,18 +847,23 @@ App::get('/v1/storage/buckets/:bucketId/files')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId')
->desc('Get file')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFile')
->label('sdk.description', '/docs/references/storage/get-file.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->label('sdk', new Method(
namespace: 'storage',
name: 'getFile',
description: '/docs/references/storage/get-file.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_FILE,
)
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
@@ -844,7 +900,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId/preview')
->desc('Get file preview')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -852,13 +908,20 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->label('cache', true)
->label('cache.resourceType', 'bucket/{request.bucketId}')
->label('cache.resource', 'file/{request.fileId}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFilePreview')
->label('sdk.description', '/docs/references/storage/get-file-preview.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->label('sdk.methodType', 'location')
->label('sdk', new Method(
namespace: 'storage',
name: 'getFilePreview',
description: '/docs/references/storage/get-file-preview.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE
)
],
type: MethodType::LOCATION,
contentType: ContentType::IMAGE
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID')
->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true)
@@ -879,7 +942,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->inject('mode')
->inject('deviceForFiles')
->inject('deviceForLocal')
->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) {
->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) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
@@ -1007,6 +1071,17 @@ 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));
}
$response
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
->setContentType($contentType)
@@ -1017,18 +1092,25 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId/download')
->desc('Get file for download')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileDownload')
->label('sdk.description', '/docs/references/storage/get-file-download.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->label('sdk', new Method(
namespace: 'storage',
name: 'getFileDownload',
description: '/docs/references/storage/get-file-download.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE
)
],
type: MethodType::LOCATION,
contentType: ContentType::ANY,
))
->param('bucketId', '', new UID(), 'Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('request')
@@ -1158,18 +1240,25 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
});
App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId/view')
->desc('Get file for view')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFileView')
->label('sdk.description', '/docs/references/storage/get-file-view.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->label('sdk', new Method(
namespace: 'storage',
name: 'getFileView',
description: '/docs/references/storage/get-file-view.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
type: MethodType::LOCATION,
contentType: ContentType::ANY,
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
@@ -1465,7 +1554,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
});
App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->alias('/v1/storage/files/:fileId')
->desc('Update file')
->groups(['api', 'storage'])
->label('scope', 'files.write')
@@ -1476,13 +1565,18 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'updateFile')
->label('sdk.description', '/docs/references/storage/update-file.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->label('sdk', new Method(
namespace: 'storage',
name: 'updateFile',
description: '/docs/references/storage/update-file.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_FILE,
)
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File unique ID.')
->param('name', null, new Text(255), 'Name of the file', true)
@@ -1555,10 +1649,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
$file->setAttribute('name', $name);
}
if ($fileSecurity && !$valid) {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
} else {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
try {
if ($fileSecurity && !$valid) {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
} else {
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$queueForEvents
@@ -1581,12 +1679,19 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteFile')
->label('sdk.description', '/docs/references/storage/delete-file.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'storage',
name: 'deleteFile',
description: '/docs/references/storage/delete-file.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
@@ -1641,10 +1746,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->setResource('file/' . $fileId)
;
if ($fileSecurity && !$valid) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
try {
if ($fileSecurity && !$valid) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
if (!$deleted) {
@@ -1669,12 +1778,18 @@ App::get('/v1/storage/usage')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_STORAGE)
->label('sdk', new Method(
namespace: 'storage',
name: 'getUsage',
description: '/docs/references/storage/get-usage.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_USAGE_STORAGE,
)
]
))
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
@@ -1749,12 +1864,18 @@ App::get('/v1/storage/:bucketId/usage')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('resourceType', RESOURCE_TYPE_BUCKETS)
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getBucketUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_BUCKETS)
->label('sdk', new Method(
namespace: 'storage',
name: 'getBucketUsage',
description: '/docs/references/storage/get-bucket-usage.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_USAGE_BUCKETS,
)
]
))
->param('bucketId', '', new UID(), 'Bucket ID.')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
+392 -225
View File
@@ -8,16 +8,23 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Platform\Workers\Deletes;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Teams;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Config\Config;
@@ -53,13 +60,18 @@ App::post('/v1/teams')
->label('scope', 'teams.write')
->label('audits.event', 'team.create')
->label('audits.resource', 'team/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/teams/create-team.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
->label('sdk', new Method(
namespace: 'teams',
name: 'create',
description: '/docs/references/teams/create-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_TEAM,
)
]
))
->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
@@ -138,14 +150,18 @@ App::get('/v1/teams')
->desc('List teams')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/teams/list-teams.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM_LIST)
->label('sdk.offline.model', '/teams')
->label('sdk', new Method(
namespace: 'teams',
name: 'list',
description: '/docs/references/teams/list-teams.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_TEAM_LIST,
)
]
))
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
@@ -202,15 +218,18 @@ App::get('/v1/teams/:teamId')
->desc('Get team')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/teams/get-team.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
->label('sdk.offline.model', '/teams')
->label('sdk.offline.key', '{teamId}')
->label('sdk', new Method(
namespace: 'teams',
name: 'get',
description: '/docs/references/teams/get-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_TEAM,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('dbForProject')
@@ -229,14 +248,18 @@ App::get('/v1/teams/:teamId/prefs')
->desc('Get team preferences')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/teams/get-team-prefs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PREFERENCES)
->label('sdk.offline.model', '/teams/{teamId}/prefs')
->label('sdk', new Method(
namespace: 'teams',
name: 'getPrefs',
description: '/docs/references/teams/get-team-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PREFERENCES,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('dbForProject')
@@ -260,15 +283,18 @@ App::put('/v1/teams/:teamId')
->label('scope', 'teams.write')
->label('audits.event', 'team.update')
->label('audits.resource', 'team/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updateName')
->label('sdk.description', '/docs/references/teams/update-team-name.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
->label('sdk.offline.model', '/teams')
->label('sdk.offline.key', '{teamId}')
->label('sdk', new Method(
namespace: 'teams',
name: 'updateName',
description: '/docs/references/teams/update-team-name.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_TEAM,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
->inject('requestTimestamp')
@@ -304,14 +330,18 @@ App::put('/v1/teams/:teamId/prefs')
->label('audits.event', 'team.update')
->label('audits.resource', 'team/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updatePrefs')
->label('sdk.description', '/docs/references/teams/update-team-prefs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PREFERENCES)
->label('sdk.offline.model', '/teams/{teamId}/prefs')
->label('sdk', new Method(
namespace: 'teams',
name: 'updatePrefs',
description: '/docs/references/teams/update-team-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PREFERENCES,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
@@ -339,12 +369,19 @@ App::delete('/v1/teams/:teamId')
->label('scope', 'teams.write')
->label('audits.event', 'team.delete')
->label('audits.resource', 'team/{request.teamId}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/teams/delete-team.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'teams',
name: 'delete',
description: '/docs/references/teams/delete-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('getProjectDB')
@@ -390,13 +427,18 @@ App::post('/v1/teams/:teamId/memberships')
->label('audits.event', 'membership.create')
->label('audits.resource', 'team/{request.teamId}')
->label('audits.userId', '{request.userId}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'createMembership')
->label('sdk.description', '/docs/references/teams/create-team-membership.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('sdk', new Method(
namespace: 'teams',
name: 'createMembership',
description: '/docs/references/teams/create-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_MEMBERSHIP,
)
]
))
->label('abuse-limit', 10)
->param('teamId', '', new UID(), 'Team ID.')
->param('email', '', new Email(), 'Email of the new team member.', true)
@@ -423,13 +465,16 @@ App::post('/v1/teams/:teamId/memberships')
->inject('queueForMails')
->inject('queueForMessaging')
->inject('queueForEvents')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) {
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
->inject('timelimit')
->inject('queueForStatsUsage')
->inject('plan')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$url = htmlentities($url);
if (empty($url)) {
if (!$isAPIKey && !$isPrivilegedUser) {
if (!$isAppUser && !$isPrivilegedUser) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'URL is required');
}
}
@@ -437,8 +482,6 @@ App::post('/v1/teams/:teamId/memberships')
if (empty($userId) && empty($email) && empty($phone)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required');
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED);
@@ -540,49 +583,60 @@ App::post('/v1/teams/:teamId/memberships')
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
}
$secret = Auth::tokenGenerator();
$membershipId = ID::unique();
$membership = new Document([
'$id' => $membershipId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::user($invitee->getId())),
Permission::update(Role::team($team->getId(), 'owner')),
Permission::delete(Role::user($invitee->getId())),
Permission::delete(Role::team($team->getId(), 'owner')),
],
'userId' => $invitee->getId(),
'userInternalId' => $invitee->getInternalId(),
'teamId' => $team->getId(),
'teamInternalId' => $team->getInternalId(),
'roles' => $roles,
'invited' => DateTime::now(),
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
'confirm' => ($isPrivilegedUser || $isAppUser),
'secret' => Auth::hash($secret),
'search' => implode(' ', [$membershipId, $invitee->getId()])
$membership = $dbForProject->findOne('memberships', [
Query::equal('userInternalId', [$invitee->getInternalId()]),
Query::equal('teamInternalId', [$team->getInternalId()]),
]);
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
try {
$membership = Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership));
} catch (Duplicate $th) {
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
}
if ($membership->isEmpty()) {
$secret = Auth::tokenGenerator();
$membershipId = ID::unique();
$membership = new Document([
'$id' => $membershipId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::user($invitee->getId())),
Permission::update(Role::team($team->getId(), 'owner')),
Permission::delete(Role::user($invitee->getId())),
Permission::delete(Role::team($team->getId(), 'owner')),
],
'userId' => $invitee->getId(),
'userInternalId' => $invitee->getInternalId(),
'teamId' => $team->getId(),
'teamInternalId' => $team->getInternalId(),
'roles' => $roles,
'invited' => DateTime::now(),
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
'confirm' => ($isPrivilegedUser || $isAppUser),
'secret' => Auth::hash($secret),
'search' => implode(' ', [$membershipId, $invitee->getId()])
]);
$membership = ($isPrivilegedUser || $isAppUser) ?
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
$dbForProject->createDocument('memberships', $membership);
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$dbForProject->purgeCachedDocument('users', $invitee->getId());
} else {
try {
$membership = $dbForProject->createDocument('memberships', $membership);
} catch (Duplicate $th) {
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
$membership->setAttribute('invited', DateTime::now());
if ($isPrivilegedUser || $isAppUser) {
$membership->setAttribute('joined', DateTime::now());
$membership->setAttribute('confirm', true);
}
$membership = ($isPrivilegedUser || $isAppUser) ?
Authorization::skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
$dbForProject->updateDocument('memberships', $membership->getId(), $membership);
}
if ($isPrivilegedUser || $isAppUser) {
$dbForProject->purgeCachedDocument('users', $invitee->getId());
} else {
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId, 'teamName' => $team->getAttribute('name')]);
$url = Template::unParseURL($url);
if (!empty($email)) {
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
@@ -650,7 +704,7 @@ App::post('/v1/teams/:teamId/memberships')
'owner' => $user->getAttribute('name'),
'direction' => $locale->getText('settings.direction'),
/* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */
'user' => $user->getAttribute('name'),
'user' => $name,
'team' => $team->getAttribute('name'),
'redirect' => $url,
'project' => $projectName
@@ -662,8 +716,8 @@ App::post('/v1/teams/:teamId/memberships')
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name'))
->setVariables($emailVariables)
->trigger()
;
->trigger();
} elseif (!empty($phone)) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
@@ -691,6 +745,27 @@ App::post('/v1/teams/:teamId/memberships')
->setMessage($messageDoc)
->setRecipients([$phone])
->setProviderType('SMS');
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
}
}
@@ -715,21 +790,25 @@ App::get('/v1/teams/:teamId/memberships')
->desc('List team memberships')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'listMemberships')
->label('sdk.description', '/docs/references/teams/list-team-members.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
->label('sdk.offline.model', '/teams/{teamId}/memberships')
->label('sdk', new Method(
namespace: 'teams',
name: 'listMemberships',
description: '/docs/references/teams/list-team-members.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MEMBERSHIP_LIST,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $teamId, array $queries, string $search, Response $response, Database $dbForProject) {
->action(function (string $teamId, array $queries, string $search, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -790,27 +869,51 @@ App::get('/v1/teams/:teamId/memberships')
$memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId')));
$memberships = array_map(function ($membership) use ($dbForProject, $team) {
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$membershipsPrivacy = [
'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true,
'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true,
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$mfa = $user->getAttribute('mfa', false);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
return $privacy || $isPrivilegedUser || $isAppUser;
}, $membershipsPrivacy);
$memberships = array_map(function ($membership) use ($dbForProject, $team, $membershipsPrivacy) {
$user = !empty(array_filter($membershipsPrivacy))
? $dbForProject->getDocument('users', $membership->getAttribute('userId'))
: new Document();
if ($membershipsPrivacy['mfa']) {
$mfa = $user->getAttribute('mfa', false);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
}
}
$membership->setAttribute('mfa', $mfa);
}
$membership
->setAttribute('mfa', $mfa)
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email'))
;
if ($membershipsPrivacy['userName']) {
$membership->setAttribute('userName', $user->getAttribute('name'));
}
if ($membershipsPrivacy['userEmail']) {
$membership->setAttribute('userEmail', $user->getAttribute('email'));
}
$membership->setAttribute('teamName', $team->getAttribute('name'));
return $membership;
}, $memberships);
@@ -825,20 +928,24 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->desc('Get team membership')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'getMembership')
->label('sdk.description', '/docs/references/teams/get-team-member.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('sdk.offline.model', '/teams/{teamId}/memberships')
->label('sdk.offline.key', '{membershipId}')
->label('sdk', new Method(
namespace: 'teams',
name: 'getMembership',
description: '/docs/references/teams/get-team-member.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MEMBERSHIP,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject) {
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -852,27 +959,50 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$membershipsPrivacy = [
'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true,
'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true,
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$mfa = $user->getAttribute('mfa', false);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
return $privacy || $isPrivilegedUser || $isAppUser;
}, $membershipsPrivacy);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
$user = !empty(array_filter($membershipsPrivacy))
? $dbForProject->getDocument('users', $membership->getAttribute('userId'))
: new Document();
if ($membershipsPrivacy['mfa']) {
$mfa = $user->getAttribute('mfa', false);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
}
}
$membership->setAttribute('mfa', $mfa);
}
$membership
->setAttribute('mfa', $mfa)
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email'))
;
if ($membershipsPrivacy['userName']) {
$membership->setAttribute('userName', $user->getAttribute('name'));
}
if ($membershipsPrivacy['userEmail']) {
$membership->setAttribute('userEmail', $user->getAttribute('email'));
}
$membership->setAttribute('teamName', $team->getAttribute('name'));
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
});
@@ -884,13 +1014,18 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->label('scope', 'teams.write')
->label('audits.event', 'membership.update')
->label('audits.resource', 'team/{request.teamId}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updateMembership')
->label('sdk.description', '/docs/references/teams/update-team-membership.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('sdk', new Method(
namespace: 'teams',
name: 'updateMembership',
description: '/docs/references/teams/update-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MEMBERSHIP,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('roles', [], function (Document $project) {
@@ -967,13 +1102,18 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->label('audits.event', 'membership.update')
->label('audits.resource', 'team/{request.teamId}')
->label('audits.userId', '{request.userId}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updateMembershipStatus')
->label('sdk.description', '/docs/references/teams/update-team-membership-status.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('sdk', new Method(
namespace: 'teams',
name: 'updateMembershipStatus',
description: '/docs/references/teams/update-team-membership-status.md',
auth: [AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_MEMBERSHIP,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('userId', '', new UID(), 'User ID.')
@@ -1012,7 +1152,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
}
if ($user->isEmpty()) {
$hasSession = !$user->isEmpty();
if (!$hasSession) {
$user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user
}
@@ -1031,39 +1172,64 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
// Log user in
// Create session for the user if not logged in
if (!$hasSession) {
Authorization::setRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
$session = $dbForProject->createDocument('sessions', $session);
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$permissions', [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
Permission::delete(Role::user($user->getId())),
]));
Authorization::setRole(Role::user($userId)->toString());
$dbForProject->purgeCachedDocument('users', $user->getId());
if (!Config::getParam('domainVerification')) {
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
}
Authorization::setRole(Role::user($userId)->toString());
$response
->addCookie(
name: Auth::$cookieName . '_legacy',
value: Auth::encodeSession($user->getId(), $secret),
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
secure: ('https' === $protocol),
httponly: true
)
->addCookie(
name: Auth::$cookieName,
value: Auth::encodeSession($user->getId(), $secret),
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
secure: ('https' === $protocol),
httponly: true,
sameSite: Config::getParam('cookieSamesite')
)
;
}
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
@@ -1077,22 +1243,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setParam('membershipId', $membership->getId())
;
if (!Config::getParam('domainVerification')) {
$response
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
$response
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
;
$response->dynamic(
$membership
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email')),
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email')),
Response::MODEL_MEMBERSHIP
);
});
@@ -1104,12 +1259,19 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
->label('scope', 'teams.write')
->label('audits.event', 'membership.delete')
->label('audits.resource', 'team/{request.teamId}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'deleteMembership')
->label('sdk.description', '/docs/references/teams/delete-team-membership.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'teams',
name: 'deleteMembership',
description: '/docs/references/teams/delete-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
@@ -1167,13 +1329,18 @@ App::get('/v1/teams/:teamId/logs')
->desc('List team logs')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'listLogs')
->label('sdk.description', '/docs/references/teams/get-team-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
->label('sdk', new Method(
namespace: 'teams',
name: 'listLogs',
description: '/docs/references/teams/get-team-logs.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_LOG_LIST,
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->inject('response')
File diff suppressed because it is too large Load Diff
+245 -199
View File
@@ -4,6 +4,11 @@ use Appwrite\Auth\OAuth2\Github as OAuth2Github;
use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Installations;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
@@ -42,7 +47,7 @@ use Utopia\VCS\Exception\RepositoryNotFound;
use function Swoole\Coroutine\batch;
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request) {
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, Request $request) {
$errors = [];
foreach ($repositories as $resource) {
try {
@@ -53,7 +58,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$projectId = $resource->getAttribute('projectId');
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
$functionId = $resource->getAttribute('resourceId');
@@ -104,7 +109,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = '';
if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) {
$latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [
$latestComment = Authorization::skip(fn () => $dbForPlatform->findOne('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerPullRequestId', [$providerPullRequestId]),
Query::orderDesc('$createdAt'),
@@ -125,7 +130,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if (!empty($latestCommentId)) {
$teamId = $project->getAttribute('teamId', '');
$latestComment = Authorization::skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([
$latestComment = Authorization::skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
@@ -146,7 +151,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
}
} elseif (!empty($providerBranch)) {
$latestComments = Authorization::skip(fn () => $dbForConsole->find('vcsComments', [
$latestComments = Authorization::skip(fn () => $dbForPlatform->find('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerBranch', [$providerBranch]),
Query::orderDesc('$createdAt'),
@@ -267,15 +272,22 @@ App::get('/v1/vcs/github/authorize')
->desc('Install GitHub app')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('error', __DIR__ . '/../../views/general/error.phtml')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'createGitHubInstallation')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.methodType', 'webAuth')
->label('sdk.hide', true)
->label('sdk', new Method(
namespace: 'vcs',
name: 'createGitHubInstallation',
description: '/docs/references/vcs/create-github-installation.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_MOVED_PERMANENTLY,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::HTML,
type: MethodType::WEBAUTH,
hide: true,
))
->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a successful installation attempt.', true, ['clients'])
->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a failed installation attempt.', true, ['clients'])
->inject('request')
@@ -319,8 +331,8 @@ App::get('/v1/vcs/github/callback')
->inject('project')
->inject('request')
->inject('response')
->inject('dbForConsole')
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForPlatform) {
if (empty($state)) {
$error = 'Installation requests from organisation members for the Appwrite GitHub App are currently unsupported. To proceed with the installation, login to the Appwrite Console and install the GitHub App.';
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error);
@@ -339,7 +351,7 @@ App::get('/v1/vcs/github/callback')
$redirectSuccess = $state['success'] ?? '';
$redirectFailure = $state['failure'] ?? '';
$project = $dbForConsole->getDocument('projects', $projectId);
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
$error = 'Project with the ID from state could not be found.';
@@ -355,53 +367,6 @@ App::get('/v1/vcs/github/callback')
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
}
$personalSlug = '';
// OAuth Authroization
if (!empty($code)) {
$oauth2 = new OAuth2Github(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''), System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''), "");
$accessToken = $oauth2->getAccessToken($code) ?? '';
$refreshToken = $oauth2->getRefreshToken($code) ?? '';
$accessTokenExpiry = $oauth2->getAccessTokenExpiry($code) ?? '';
$personalSlug = $oauth2->getUserSlug($accessToken) ?? '';
$email = $oauth2->getUserEmail($accessToken);
$oauth2ID = $oauth2->getUserID($accessToken);
// Makes sure this email is not already used in another identity
$identity = $dbForConsole->findOne('identities', [
Query::equal('providerEmail', [$email]),
]);
if (!$identity->isEmpty()) {
if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
$identity = $identity
->setAttribute('providerAccessToken', $accessToken)
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
} else {
$identity = $dbForConsole->createDocument('identities', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::user($user->getId())),
Permission::delete(Role::user($user->getId())),
],
'userInternalId' => $user->getInternalId(),
'userId' => $user->getId(),
'provider' => 'github',
'providerUid' => $oauth2ID,
'providerEmail' => $email,
'providerAccessToken' => $accessToken,
'providerRefreshToken' => $refreshToken,
'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry),
]));
}
}
// Create / Update installation
if (!empty($providerInstallationId)) {
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
@@ -411,11 +376,27 @@ App::get('/v1/vcs/github/callback')
$projectInternalId = $project->getInternalId();
$installation = $dbForConsole->findOne('installations', [
$installation = $dbForPlatform->findOne('installations', [
Query::equal('providerInstallationId', [$providerInstallationId]),
Query::equal('projectInternalId', [$projectInternalId])
]);
$personal = false;
$refreshToken = null;
$accessToken = null;
$accessTokenExpiry = null;
if (!empty($code)) {
$oauth2 = new OAuth2Github(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''), System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''), "");
$accessToken = $oauth2->getAccessToken($code) ?? '';
$refreshToken = $oauth2->getRefreshToken($code) ?? '';
$accessTokenExpiry = DateTime::addSeconds(new \DateTime(), \intval($oauth2->getAccessTokenExpiry($code)));
$personalSlug = $oauth2->getUserSlug($accessToken) ?? '';
$personal = $personalSlug === $owner;
}
if ($installation->isEmpty()) {
$teamId = $project->getAttribute('teamId', '');
@@ -433,15 +414,21 @@ App::get('/v1/vcs/github/callback')
'projectInternalId' => $projectInternalId,
'provider' => 'github',
'organization' => $owner,
'personal' => $personalSlug === $owner
'personal' => $personal,
'personalRefreshToken' => $refreshToken,
'personalAccessToken' => $accessToken,
'personalAccessTokenExpiry' => $accessTokenExpiry,
]);
$installation = $dbForConsole->createDocument('installations', $installation);
$installation = $dbForPlatform->createDocument('installations', $installation);
} else {
$installation = $installation
->setAttribute('organization', $owner)
->setAttribute('personal', $personalSlug === $owner);
$installation = $dbForConsole->updateDocument('installations', $installation->getId(), $installation);
->setAttribute('personal', $personal)
->setAttribute('personalRefreshToken', $refreshToken)
->setAttribute('personalAccessToken', $accessToken)
->setAttribute('personalAccessTokenExpiry', $accessTokenExpiry);
$installation = $dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
}
} else {
$error = 'Installation of the Appwrite GitHub App on organization accounts is restricted to organization owners. As a member of the organization, you do not have the necessary permissions to install this GitHub App. Please contact the organization owner to create the installation from the Appwrite console.';
@@ -467,22 +454,27 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
->desc('Get files and directories of a VCS repository')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'getRepositoryContents')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VCS_CONTENT_LIST)
->label('sdk', new Method(
namespace: 'vcs',
name: 'getRepositoryContents',
description: '/docs/references/vcs/get-repository-contents.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VCS_CONTENT_LIST,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
->param('providerRootDirectory', '', new Text(256, 0), 'Path to get contents of nested directory', true)
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->inject('dbForPlatform')
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
@@ -528,22 +520,27 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
->desc('Detect runtime settings from source code')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'createRepositoryDetection')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DETECTION)
->label('sdk', new Method(
namespace: 'vcs',
name: 'createRepositoryDetection',
description: '/docs/references/vcs/create-repository-detection.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DETECTION,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
->param('providerRootDirectory', '', new Text(256, 0), 'Path to Root Directory', true)
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->inject('dbForPlatform')
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
@@ -600,25 +597,30 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
->desc('List repositories')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'listRepositories')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROVIDER_REPOSITORY_LIST)
->label('sdk', new Method(
namespace: 'vcs',
name: 'listRepositories',
description: '/docs/references/vcs/list-repositories.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROVIDER_REPOSITORY_LIST,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (string $installationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
if (empty($search)) {
$search = "";
}
$installation = $dbForConsole->getDocument('installations', $installationId);
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
@@ -695,13 +697,18 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
->desc('Create repository')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'createRepository')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROVIDER_REPOSITORY)
->label('sdk', new Method(
namespace: 'vcs',
name: 'createRepository',
description: '/docs/references/vcs/create-repository.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROVIDER_REPOSITORY,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('name', '', new Text(256), 'Repository name (slug)')
->param('private', '', new Boolean(false), 'Mark repository public or private')
@@ -709,9 +716,9 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
->inject('user')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, string $name, bool $private, GitHub $github, Document $user, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->inject('dbForPlatform')
->action(function (string $installationId, string $name, bool $private, GitHub $github, Document $user, Response $response, Document $project, Database $dbForPlatform) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
@@ -720,17 +727,23 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
if ($installation->getAttribute('personal', false) === true) {
$oauth2 = new OAuth2Github(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''), System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''), "");
$identity = $dbForConsole->findOne('identities', [
Query::equal('provider', ['github']),
Query::equal('userInternalId', [$user->getInternalId()]),
]);
if ($identity->isEmpty()) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$accessToken = $installation->getAttribute('personalAccessToken');
$refreshToken = $installation->getAttribute('personalRefreshToken');
$accessTokenExpiry = $installation->getAttribute('personalAccessTokenExpiry');
$accessToken = $identity->getAttribute('providerAccessToken');
$refreshToken = $identity->getAttribute('providerRefreshToken');
$accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry');
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
$identity = $dbForPlatform->findOne('identities', [
Query::equal('provider', ['github']),
Query::equal('userInternalId', [$user->getInternalId()]),
]);
if ($identity->isEmpty()) {
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
}
$accessToken = $accessToken ?? $identity->getAttribute('providerAccessToken');
$refreshToken = $refreshToken ?? $identity->getAttribute('providerRefreshToken');
$accessTokenExpiry = $accessTokenExpiry ?? $identity->getAttribute('providerAccessTokenExpiry');
}
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
if ($isExpired) {
@@ -745,12 +758,12 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, "Another request is currently refreshing OAuth token. Please try again.");
}
$identity = $identity
->setAttribute('providerAccessToken', $accessToken)
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
$installation = $installation
->setAttribute('personalAccessToken', $accessToken)
->setAttribute('personalRefreshToken', $refreshToken)
->setAttribute('personalAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
$dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
}
try {
@@ -796,21 +809,26 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
->desc('Get repository')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'getRepository')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROVIDER_REPOSITORY)
->label('sdk', new Method(
namespace: 'vcs',
name: 'getRepository',
description: '/docs/references/vcs/get-repository.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROVIDER_REPOSITORY,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->inject('dbForPlatform')
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
@@ -845,21 +863,26 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
->desc('List repository branches')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'listRepositoryBranches')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BRANCH_LIST)
->label('sdk', new Method(
namespace: 'vcs',
name: 'listRepositoryBranches',
description: '/docs/references/vcs/list-repository-branches.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_BRANCH_LIST,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->inject('dbForPlatform')
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
@@ -897,11 +920,11 @@ App::post('/v1/vcs/github/events')
->inject('gitHub')
->inject('request')
->inject('response')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForBuilds')
->action(
function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
$payload = $request->getRawPayload();
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
@@ -935,36 +958,36 @@ App::post('/v1/vcs/github/events')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
//find functionId from functions table
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::limit(100),
]));
// create new deployment only on push and not when branch is created
if (!$providerBranchCreated) {
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
}
} elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") {
// TODO: Use worker for this job instead (update function as well)
$providerInstallationId = $parsedPayload["installationId"];
$installations = $dbForConsole->find('installations', [
$installations = $dbForPlatform->find('installations', [
Query::equal('providerInstallationId', [$providerInstallationId]),
Query::limit(1000)
]);
foreach ($installations as $installation) {
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('installationInternalId', [$installation->getInternalId()]),
Query::limit(1000)
]));
foreach ($repositories as $repository) {
Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
Authorization::skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
}
$dbForConsole->deleteDocument('installations', $installation->getId());
$dbForPlatform->deleteDocument('installations', $installation->getId());
}
}
} elseif ($event == $github::EVENT_PULL_REQUEST) {
@@ -993,12 +1016,12 @@ App::post('/v1/vcs/github/events')
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]));
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
} elseif ($parsedPayload["action"] == "closed") {
// Allowed external contributions cleanup
@@ -1007,7 +1030,7 @@ App::post('/v1/vcs/github/events')
$external = $parsedPayload["external"] ?? true;
if ($external) {
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]));
@@ -1018,7 +1041,7 @@ App::post('/v1/vcs/github/events')
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
}
}
}
@@ -1033,20 +1056,25 @@ App::get('/v1/vcs/installations')
->desc('List installations')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'listInstallations')
->label('sdk.description', '/docs/references/vcs/list-installations.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INSTALLATION_LIST)
->label('sdk', new Method(
namespace: 'vcs',
name: 'listInstallations',
description: '/docs/references/vcs/list-installations.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_INSTALLATION_LIST,
)
]
))
->param('queries', [], new Installations(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Installations::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -1075,7 +1103,7 @@ App::get('/v1/vcs/installations')
}
$installationId = $cursor->getValue();
$cursorDocument = $dbForConsole->getDocument('installations', $installationId);
$cursorDocument = $dbForPlatform->getDocument('installations', $installationId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Installation '{$installationId}' for the 'cursor' value not found.");
@@ -1086,8 +1114,8 @@ App::get('/v1/vcs/installations')
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForConsole->find('installations', $queries);
$total = $dbForConsole->count('installations', $filterQueries, APP_LIMIT_COUNT);
$results = $dbForPlatform->find('installations', $queries);
$total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
'installations' => $results,
@@ -1099,19 +1127,24 @@ App::get('/v1/vcs/installations/:installationId')
->desc('Get installation')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'getInstallation')
->label('sdk.description', '/docs/references/vcs/get-installation.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INSTALLATION)
->label('sdk', new Method(
namespace: 'vcs',
name: 'getInstallation',
description: '/docs/references/vcs/get-installation.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_INSTALLATION,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->inject('dbForPlatform')
->action(function (string $installationId, Response $response, Document $project, Database $dbForPlatform) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation === false || $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
@@ -1128,25 +1161,32 @@ App::delete('/v1/vcs/installations/:installationId')
->desc('Delete installation')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'deleteInstallation')
->label('sdk.description', '/docs/references/vcs/delete-installation.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'vcs',
name: 'deleteInstallation',
description: '/docs/references/vcs/delete-installation.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('installationId', '', new Text(256), 'Installation Id')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('queueForDeletes')
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->action(function (string $installationId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!$dbForConsole->deleteDocument('installations', $installation->getId())) {
if (!$dbForPlatform->deleteDocument('installations', $installation->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB');
}
@@ -1161,12 +1201,18 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
->desc('Authorize external deployment')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
->label('sdk.namespace', 'vcs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.method', 'updateExternalDeployments')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk', new Method(
namespace: 'vcs',
name: 'updateExternalDeployments',
description: '/docs/references/vcs/update-external-deployments.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
]
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('repositoryId', '', new Text(256), 'VCS Repository Id')
->param('providerPullRequestId', '', new Text(256), 'GitHub Pull Request Id')
@@ -1174,17 +1220,17 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
->inject('request')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForBuilds')
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
$installation = $dbForConsole->getDocument('installations', $installationId);
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
$repository = Authorization::skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]));
@@ -1201,7 +1247,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
// TODO: Delete from array when PR is closed
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
@@ -1225,7 +1271,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
$response->noContent();
});
+156 -71
View File
@@ -7,9 +7,13 @@ use Appwrite\Auth\Auth;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Usage;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Network\Validator\Origin;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
@@ -21,6 +25,7 @@ use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
use Appwrite\Utopia\View;
use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Database\DetectsLostConnections;
use Swoole\Http\Request as SwooleRequest;
use Utopia\App;
use Utopia\CLI\Console;
@@ -28,6 +33,7 @@ use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception as DatabaseException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
@@ -38,6 +44,7 @@ use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Logger\Logger;
use Utopia\Pools\Connection;
use Utopia\System\System;
use Utopia\Validator\Hostname;
use Utopia\Validator\Text;
@@ -46,20 +53,28 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked)
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname)
{
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
$host = $previewHostname;
}
$route = Authorization::skip(
fn () => $dbForConsole->find('rules', [
Query::equal('domain', [$host]),
Query::limit(1)
])
)[0] ?? null;
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$route = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
} else {
$route = Authorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$host]),
Query::limit(1)
])
)[0] ?? new Document();
}
if ($route === null) {
if ($route->isEmpty()) {
if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.');
}
@@ -69,7 +84,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
}
if (System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled') {
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL && $host !== System::getEnv('_APP_CONSOLE_DOMAIN', '')) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.');
}
}
@@ -81,8 +96,17 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$projectId = $route->getAttribute('projectId');
$project = Authorization::skip(
fn () => $dbForConsole->getDocument('projects', $projectId)
fn () => $dbForPlatform->getDocument('projects', $projectId)
);
if (!$project->isEmpty() && $project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
}
}
if (array_key_exists('proxy', $project->getAttribute('services', []))) {
$status = $project->getAttribute('services', [])['proxy'];
if (!$status) {
@@ -99,8 +123,29 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$type = $route->getAttribute('resourceType');
if ($type === 'function') {
$utopia->getRoute()?->label('sdk.namespace', 'functions');
$utopia->getRoute()?->label('sdk.method', 'createExecution');
$method = $utopia->getRoute()?->getLabel('sdk', null);
if (empty($method)) {
$utopia->getRoute()?->label('sdk', new Method(
namespace: 'functions',
name: 'createExecution',
description: '/docs/references/functions/create-execution.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_EXECUTION,
)
],
contentType: ContentType::MULTIPART,
requestType: 'application/json',
));
} else {
/** @var Method $method */
$method->setNamespace('functions');
$method->setMethodName('createExecution');
$utopia->getRoute()?->label('sdk', $method);
}
if (System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
@@ -127,7 +172,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$requestHeaders = $request->getHeaders();
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
@@ -352,26 +397,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
throw $th;
}
} finally {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
}
$queueForUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->setProject($project)
->trigger()
;
$queueForFunctions
->setType(Func::TYPE_ASYNC_WRITE)
->setExecution($execution)
@@ -406,6 +431,26 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
->setStatusCode($execution['responseStatusCode'] ?? 200)
->send($body);
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
}
$queueForStatsUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->setProject($project)
->trigger()
;
return true;
} elseif ($type === 'api') {
$utopia->getRoute()?->label('error', '');
@@ -451,26 +496,27 @@ App::init()
->inject('response')
->inject('console')
->inject('project')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('locale')
->inject('localeCodes')
->inject('clients')
->inject('geodb')
->inject('queueForUsage')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForCertificates')
->inject('queueForFunctions')
->inject('isResourceBlocked')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked) {
->inject('previewHostname')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $previewHostname) {
/*
* Appwrite Router
*/
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain) {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
if ($host !== $mainDomain || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
return;
}
}
@@ -518,19 +564,31 @@ App::init()
if (!empty($envDomain) && $envDomain !== 'localhost') {
$mainDomain = $envDomain;
} else {
$domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]);
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$domainDocument = $dbForPlatform->getDocument('rules', md5($envDomain));
} else {
$domainDocument = $dbForPlatform->findOne('rules', [Query::orderAsc('$id')]);
}
$mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get();
}
if ($mainDomain !== $domain->get()) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
} else {
$domainDocument = $dbForConsole->findOne('rules', [
Query::equal('domain', [$domain->get()])
]);
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$domainDocument = $dbForPlatform->getDocument('rules', md5($domain->get()));
} else {
$domainDocument = $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain->get()])
]);
}
if ($domainDocument->isEmpty()) {
$domainDocument = new Document([
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
'$id' => System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(),
'domain' => $domain->get(),
'resourceType' => 'api',
'status' => 'verifying',
@@ -538,7 +596,7 @@ App::init()
'projectInternalId' => 'console'
]);
$domainDocument = $dbForConsole->createDocument('rules', $domainDocument);
$domainDocument = $dbForPlatform->createDocument('rules', $domainDocument);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
@@ -674,22 +732,23 @@ App::options()
->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('geodb')
->inject('isResourceBlocked')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $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) {
/*
* Appwrite Router
*/
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain) {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
if ($host !== $mainDomain || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
return;
}
}
@@ -714,8 +773,13 @@ App::error()
->inject('project')
->inject('logger')
->inject('log')
->inject('queueForUsage')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage) {
->inject('queueForStatsUsage')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) {
if (
($error instanceof PDOException || $error instanceof DatabaseException)
&& DetectsLostConnections::causedByLostConnection($error)
) {
}
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute();
$class = \get_class($error);
@@ -775,6 +839,12 @@ App::error()
case 'Utopia\Database\Exception\Relationship':
$error = new AppwriteException(AppwriteException::RELATIONSHIP_VALUE_INVALID, $error->getMessage(), previous: $error);
break;
case 'Utopia\Database\Exception\NotFound':
$error = new AppwriteException(AppwriteException::COLLECTION_NOT_FOUND, $error->getMessage(), previous: $error);
break;
case 'Utopia\Database\Exception\Dependency':
$error = new AppwriteException(AppwriteException::INDEX_DEPENDENCY, null, previous: $error);
break;
}
$code = $error->getCode();
@@ -802,7 +872,7 @@ App::error()
$adapter = new Sentry($projectId, $key, $host);
$logger = new Logger($adapter);
$logger->setSample(0.04);
$logger->setSample(0.01);
$publish = true;
} else {
throw new \Exception('Invalid experimental logging provider');
@@ -820,13 +890,13 @@ App::error()
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
}
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize());
}
$queueForUsage
$queueForStatsUsage
->setProject($project)
->trigger();
}
@@ -842,6 +912,8 @@ App::error()
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
} else {
$log->setUser(new User('guest-' . hash('sha256', $request->getIP())));
}
try {
@@ -852,14 +924,14 @@ App::error()
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('database', $dsn->getHost());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('url', $request->getURI());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
@@ -871,8 +943,14 @@ App::error()
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
if (!empty($sdk)) {
/** @var Appwrite\SDK\Method $sdk */
$action = $sdk->getNamespace() . '.' . $sdk->getMethodName();
}
$log->setAction($action);
$log->addTag('service', $action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
@@ -967,22 +1045,23 @@ App::get('/robots.txt')
->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('geodb')
->inject('isResourceBlocked')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $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) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
if ($host === $mainDomain || $host === 'localhost') {
if (($host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked);
router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname);
}
});
@@ -994,22 +1073,23 @@ App::get('/humans.txt')
->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('geodb')
->inject('isResourceBlocked')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $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) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
if ($host === $mainDomain || $host === 'localhost') {
if (($host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked);
router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname);
}
});
@@ -1073,9 +1153,9 @@ App::get('/v1/ping')
->label('event', 'projects.[projectId].ping')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('queueForEvents')
->action(function (Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) {
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) {
if ($project->isEmpty()) {
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
}
@@ -1087,8 +1167,8 @@ App::get('/v1/ping')
->setAttribute('pingCount', $pingCount)
->setAttribute('pingedAt', $pingedAt);
Authorization::skip(function () use ($dbForConsole, $project) {
$dbForConsole->updateDocument('projects', $project->getId(), $project);
Authorization::skip(function () use ($dbForPlatform, $project) {
$dbForPlatform->updateDocument('projects', $project->getId(), $project);
});
$queueForEvents
@@ -1108,3 +1188,8 @@ App::wildcard()
foreach (Config::getParam('services', []) as $service) {
include_once $service['controller'];
}
// Check for any errors found while we were initialising the SDK Methods.
if (!empty(Method::getErrors())) {
throw new \Exception('Errors found during SDK initialization:' . PHP_EOL . implode(PHP_EOL, Method::getErrors()));
}
+11 -11
View File
@@ -24,7 +24,7 @@ App::get('/v1/mock/tests/general/oauth2')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
->label('sdk.mock', true)
->label('mock', true)
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
->param('scope', '', new Text(100), 'OAuth2 scope list.')
@@ -40,7 +40,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
->label('sdk.mock', true)
->label('mock', true)
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true)
@@ -162,15 +162,15 @@ App::post('/v1/mock/api-key-unprefixed')
->label('docs', false)
->param('projectId', '', new UID(), 'Project ID.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
if (!$isDevelopment) {
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
}
$project = $dbForConsole->getDocument('projects', $projectId);
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
@@ -195,9 +195,9 @@ App::post('/v1/mock/api-key-unprefixed')
'secret' => \bin2hex(\random_bytes(128)),
]);
$key = $dbForConsole->createDocument('keys', $key);
$key = $dbForPlatform->createDocument('keys', $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -214,15 +214,15 @@ App::get('/v1/mock/github/callback')
->inject('gitHub')
->inject('project')
->inject('response')
->inject('dbForConsole')
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) {
->inject('dbForPlatform')
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForPlatform) {
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
if (!$isDevelopment) {
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
}
$project = $dbForConsole->getDocument('projects', $projectId);
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
$error = 'Project with the ID from state could not be found.';
@@ -256,7 +256,7 @@ App::get('/v1/mock/github/callback')
'personal' => false
]);
$installation = $dbForConsole->createDocument('installations', $installation);
$installation = $dbForPlatform->createDocument('installations', $installation);
}
$response->json([
+193 -121
View File
@@ -11,14 +11,14 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Messaging;
use Appwrite\Event\Usage;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
@@ -28,6 +28,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Queue\Publisher;
use Utopia\System\System;
use Utopia\Validator\WhiteList;
@@ -57,8 +58,39 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
return $label;
};
$databaseListener = function (string $event, Document $document, Document $project, Usage $queueForUsage, Database $dbForProject) {
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
// Only trigger events for user creation with the database listener.
if ($document->getCollection() !== 'users') {
return;
}
$queueForEvents
->setEvent('users.[userId].create')
->setParam('userId', $document->getId())
->setPayload($response->output($document, Response::MODEL_USER));
// Trigger functions, webhooks, and realtime events
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger webhooks events only if a project has them enabled */
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
/** Trigger realtime events only for non console events */
if ($queueForEvents->getProject()->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
};
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$value = 1;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$value = -1;
@@ -66,40 +98,40 @@ $databaseListener = function (string $event, Document $document, Document $proje
switch (true) {
case $document->getCollection() === 'teams':
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_TEAMS, $value); // per project
break;
case $document->getCollection() === 'users':
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'sessions': // sessions
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_SESSIONS, $value); //per project
break;
case $document->getCollection() === 'databases': // databases
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_DATABASES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
$queueForStatsUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value)
;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
$queueForStatsUsage
->addReduce($document);
}
break;
@@ -107,39 +139,39 @@ $databaseListener = function (string $event, Document $document, Document $proje
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_DOCUMENTS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
break;
case $document->getCollection() === 'buckets': //buckets
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
$queueForStatsUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_FUNCTION_ID_DEPLOYMENTS), $value) // per function
@@ -154,14 +186,16 @@ App::init()
->groups(['api'])
->inject('utopia')
->inject('request')
->inject('dbForConsole')
->inject('dbForPlatform')
->inject('dbForProject')
->inject('queueForAudits')
->inject('project')
->inject('user')
->inject('session')
->inject('servers')
->inject('mode')
->inject('team')
->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
$route = $utopia->getRoute();
if ($project->isEmpty()) {
@@ -197,7 +231,7 @@ App::init()
if ($keyType === API_KEY_DYNAMIC) {
// Dynamic key
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400, 0);
try {
$payload = $jwtObj->decode($authKey);
@@ -213,9 +247,10 @@ App::init()
$user = new Document([
'$id' => '',
'status' => true,
'type' => Auth::ACTIVITY_TYPE_APP,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
'name' => 'Dynamic Key',
]);
$role = Auth::USER_ROLE_APPS;
@@ -223,6 +258,8 @@ App::init()
Authorization::setRole(Auth::USER_ROLE_APPS);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
$queueForAudits->setUser($user);
}
} elseif ($keyType === API_KEY_STANDARD) {
// No underline means no prefix. Backwards compatibility.
@@ -234,9 +271,10 @@ App::init()
$user = new Document([
'$id' => '',
'status' => true,
'type' => Auth::ACTIVITY_TYPE_APP,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
'name' => $key->getAttribute('name', 'UNKNOWN'),
]);
$role = Auth::USER_ROLE_APPS;
@@ -253,8 +291,8 @@ App::init()
$accessedAt = $key->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DateTime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$dbForPlatform->updateDocument('keys', $key->getId(), $key);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
$sdkValidator = new WhiteList($servers, true);
@@ -267,10 +305,12 @@ App::init()
/** Update access time as well */
$key->setAttribute('accessedAt', Datetime::now());
$dbForConsole->updateDocument('keys', $key->getId(), $key);
$dbForConsole->purgeCachedDocument('projects', $project->getId());
$dbForPlatform->updateDocument('keys', $key->getId(), $key);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
}
$queueForAudits->setUser($user);
}
}
}
@@ -305,12 +345,48 @@ App::init()
Authorization::setRole($authRole);
}
/**
* Update project last activity
*/
if (!$project->isEmpty() && $project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
}
}
/**
* Update user last activity
*/
if (!empty($user->getId())) {
$accessedAt = $user->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
$user->setAttribute('accessedAt', DateTime::now());
if (APP_MODE_ADMIN !== $mode) {
$dbForProject->updateDocument('users', $user->getId(), $user);
} else {
$dbForPlatform->updateDocument('users', $user->getId(), $user);
}
}
}
/** Do not allow access to disabled services */
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
/**
* @var ?\Appwrite\SDK\Method $method
*/
$method = $route->getLabel('sdk', false);
if (is_array($method)) {
$method = $method[0];
}
if (!empty($method)) {
$namespace = $method->getNamespace();
if (
array_key_exists($service, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$service]
array_key_exists($namespace, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$namespace]
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
@@ -353,16 +429,18 @@ App::init()
->inject('response')
->inject('project')
->inject('user')
->inject('publisher')
->inject('queueForEvents')
->inject('queueForMessaging')
->inject('queueForAudits')
->inject('queueForDeletes')
->inject('queueForDatabase')
->inject('queueForBuilds')
->inject('queueForUsage')
->inject('queueForStatsUsage')
->inject('dbForProject')
->inject('timelimit')
->inject('mode')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) {
$route = $utopia->getRoute();
@@ -385,7 +463,7 @@ App::init()
foreach ($abuseKeyLabel as $abuseKey) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
$timeLimit
->setParam('{projectId}', $project->getId())
->setParam('{userId}', $user->getId())
@@ -413,7 +491,7 @@ App::init()
$abuse = new Abuse($timeLimit);
$remaining = $timeLimit->remaining();
$limit = $timeLimit->limit();
$time = (new \DateTime($timeLimit->time()))->getTimestamp() + $route->getLabel('abuse-time', 3600);
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
$closestLimit = $remaining;
@@ -447,18 +525,42 @@ App::init()
->setMode($mode)
->setUserAgent($request->getUserAgent(''))
->setIP($request->getIP())
->setHostname($request->getHostname())
->setEvent($route->getLabel('audits.event', ''))
->setProject($project)
->setUser($user);
->setProject($project);
/* If a session exists, use the user associated with the session */
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
}
$queueForDeletes->setProject($project);
$queueForDatabase->setProject($project);
$queueForBuilds->setProject($project);
$queueForMessaging->setProject($project);
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.
$queueForEventsClone = new Event($publisher);
$queueForFunctions = new Func($publisher);
$queueForWebhooks = new Webhook($publisher);
$queueForRealtime = new Realtime();
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject));
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
$response,
$queueForEventsClone->from($queueForEvents),
$queueForFunctions->from($queueForEvents),
$queueForWebhooks->from($queueForEvents),
$queueForRealtime->from($queueForEvents)
));
$useCache = $route->getLabel('cache', false);
if ($useCache) {
@@ -505,6 +607,12 @@ 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));
}
}
$response
@@ -586,16 +694,16 @@ App::shutdown()
->inject('user')
->inject('queueForEvents')
->inject('queueForAudits')
->inject('queueForUsage')
->inject('queueForStatsUsage')
->inject('queueForDeletes')
->inject('queueForDatabase')
->inject('queueForBuilds')
->inject('queueForMessaging')
->inject('dbForProject')
->inject('queueForFunctions')
->inject('mode')
->inject('dbForConsole')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
->inject('queueForWebhooks')
->inject('queueForRealtime')
->inject('dbForProject')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -604,54 +712,25 @@ App::shutdown()
$queueForEvents->setPayload($responsePayload);
}
/**
* Trigger functions.
*/
if (!$queueForEvents->isPaused()) {
$queueForFunctions
$queueForFunctions
->from($queueForEvents)
->trigger();
if ($project->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
/**
* Trigger webhooks.
*/
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->trigger();
/**
* Trigger realtime.
*/
if ($project->getId() !== 'console') {
$allEvents = Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams());
$payload = new Document($queueForEvents->getPayload());
$db = $queueForEvents->getContext('database');
$collection = $queueForEvents->getContext('collection');
$bucket = $queueForEvents->getContext('bucket');
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $payload,
project: $project,
database: $db,
collection: $collection,
bucket: $bucket,
);
Realtime::send(
projectId: $target['projectId'] ?? $project->getId(),
payload: $queueForEvents->getRealtimePayload(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $queueForEvents->getParam('userId')
]
);
/** Trigger webhooks events only if a project has them enabled
* A future optimisation is to only trigger webhooks if the webhook is "enabled"
* But it might have performance implications on the API due to the number of webhooks etc.
* Some profiling is needed to see if this is a problem.
*/
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
}
@@ -670,10 +749,32 @@ App::shutdown()
}
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
/**
* User in the request is empty, and no user was set for auditing previously.
* This indicates:
* - No API Key was used.
* - No active session exists.
*
* Therefore, we consider this an anonymous request and create a relevant user.
*/
$user = new Document([
'$id' => '',
'status' => true,
'type' => Auth::ACTIVITY_TYPE_GUEST,
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => 'Guest',
]);
$queueForAudits->setUser($user);
}
if (!empty($queueForAudits->getResource()) && !empty($queueForAudits->getUser()->getId())) {
if (!empty($queueForAudits->getResource()) && !$queueForAudits->getUser()->isEmpty()) {
/**
* audits.payload is switched to default true
* in order to auto audit payload for all endpoints
@@ -751,8 +852,6 @@ App::shutdown()
}
}
if ($project->getId() !== 'console') {
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
$fileSize = 0;
@@ -761,43 +860,16 @@ App::shutdown()
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
}
$queueForUsage
$queueForStatsUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize());
}
$queueForUsage
$queueForStatsUsage
->setProject($project)
->trigger();
}
/**
* Update project last activity
*/
if (!$project->isEmpty() && $project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
}
}
/**
* Update user last activity
*/
if (!$user->isEmpty()) {
$accessedAt = $user->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
$user->setAttribute('accessedAt', DateTime::now());
if (APP_MODE_ADMIN !== $mode) {
$dbForProject->updateDocument('users', $user->getId(), $user);
} else {
$dbForConsole->updateDocument('users', $user->getId(), $user);
}
}
}
});
App::init()
+9 -8
View File
@@ -5,6 +5,7 @@ use Appwrite\Extend\Exception;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
@@ -57,44 +58,44 @@ App::init()
$auths = $project->getAttribute('auths', []);
switch ($route->getLabel('auth.type', '')) {
case 'emailPassword':
if (($auths['emailPassword'] ?? true) === false) {
case 'email-password':
if (($auths[Config::getParam('auth')['email-password']['key']] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email / Password authentication is disabled for this project');
}
break;
case 'magic-url':
if (($auths['usersAuthMagicURL'] ?? true) === false) {
if (($auths[Config::getParam('auth')['magic-url']['key']] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
}
break;
case 'anonymous':
if (($auths['anonymous'] ?? true) === false) {
if (($auths[Config::getParam('auth')['anonymous']['key']] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Anonymous authentication is disabled for this project');
}
break;
case 'phone':
if (($auths['phone'] ?? true) === false) {
if (($auths[Config::getParam('auth')['phone']['key']] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
}
break;
case 'invites':
if (($auths['invites'] ?? true) === false) {
if (($auths[Config::getParam('auth')['invites']['key']] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
}
break;
case 'jwt':
if (($auths['JWT'] ?? true) === false) {
if (($auths[Config::getParam('auth')['jwt']['key']] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'JWT authentication is disabled for this project');
}
break;
case 'email-otp':
if (($auths['emailOTP'] ?? true) === false) {
if (($auths[Config::getParam('auth')['email-otp']['key']] ?? true) === false) {
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email OTP authentication is disabled for this project');
}
break;
+310 -137
View File
@@ -5,20 +5,25 @@ require_once __DIR__ . '/../vendor/autoload.php';
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Swoole\Constant;
use Swoole\Database\DetectsLostConnections;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Swoole\Http\Server;
use Swoole\Process;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Swoole\Table;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception as DatabaseException;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
@@ -26,6 +31,12 @@ use Utopia\Pools\Group;
use Utopia\Swoole\Files;
use Utopia\System\System;
const DOMAIN_SYNC_TIMER = 30; // 30 seconds
$domains = new Table(1_000_000); // 1 million rows
$domains->column('value', Table::TYPE_INT, 1);
$domains->create();
$http = new Server(
host: "0.0.0.0",
port: System::getEnv('PORT', 80),
@@ -33,16 +44,17 @@ $http = new Server(
);
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$http
->set([
'worker_num' => $workerNumber,
'worker_num' => $totalWorkers,
'dispatch_func' => 'dispatch',
'open_http2_protocol' => true,
'http_compression' => true,
'http_compression_level' => 6,
'http_compression' => false,
'package_max_length' => $payloadSize,
'buffer_output_size' => $payloadSize,
'task_worker_num' => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
@@ -57,8 +69,168 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
/**
* Assigns HTTP requests to worker threads by analyzing its payload/content.
*
* Routes requests as 'safe' or 'risky' based on specific content patterns (like POST actions or certain domains)
* to optimize load distribution between the workers. Utilizes `$safeThreadsPercent` to manage risk by assigning
* riskier tasks to a dedicated worker subset. Prefers idle workers, with fallback to random selection if necessary.
* doc: https://openswoole.com/docs/modules/swoole-server/configuration#dispatch_func
*
* @param Server $server Swoole server instance.
* @param int $fd client ID
* @param int $type the type of data and its current state
* @param string|null $data Request content for categorization.
* @global int $totalThreads Total number of workers.
* @return int Chosen worker ID for the request.
*/
function dispatch(Server $server, int $fd, int $type, $data = null): int
{
global $totalWorkers, $domains;
// If data is not set we can send request to any worker
// first we try to pick idle worker, if not we randomly pick a worker
if ($data === null) {
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
return rand(0, $totalWorkers - 1);
}
$riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1
// Each worker has numeric ID, starting from 0 and incrementing
// From 0 to riskyWorkers, we consider safe workers
// From riskyWorkers to totalWorkers, we consider risky workers
$riskyWorkers = (int) floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers
$domain = '';
// max up to 3 as first line has request details and second line has host
$lines = explode("\n", $data, 3);
$request = $lines[0];
if (count($lines) > 1) {
$domain = trim(explode('Host: ', $lines[1])[1]);
}
// Sync executions are considered risky
$risky = false;
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
$risky = true;
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
$risky = true;
} elseif ($domains->get(md5($domain), 'value') === 1) {
// executions request coming from custom domain
$risky = true;
}
if ($risky) {
// If risky request, only consider risky workers
for ($j = $riskyWorkers; $j < $totalWorkers; $j++) {
/** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */
if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) {
// If idle worker found, give to him
return $j;
}
}
// If no idle workers, give to random risky worker
$worker = rand($riskyWorkers, $totalWorkers - 1);
Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
}
// If safe request, give to any idle worker
// Its fine to pick risky worker here, because it's idle. Idle is never actually risky
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
// If no idle worker found, give to random safe worker
// We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky.
$worker = rand(0, $riskyWorkers - 1);
Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
}
include __DIR__ . '/controllers/general.php';
function createDatabase(App $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
{
$max = 10;
$sleep = 1;
$attempts = 0;
do {
try {
$attempts++;
$resource = $app->getResource($resourceKey);
/* @var $database Database */
$database = is_callable($resource) ? $resource() : $resource;
break; // exit loop on success
} catch (\Exception $e) {
Console::warning(" └── Database not ready. Retrying connection ({$attempts})...");
$pools->reclaim();
if ($attempts >= $max) {
throw new \Exception(' └── Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
}
} while ($attempts < $max);
Console::success("[Setup] - $dbName database init started...");
// Attempt to create the database
try {
Console::info(" └── Creating database: $dbName...");
$database->create();
} catch (\Exception $e) {
Console::info(" └── Skip: metadata table already exists");
}
// Process collections
foreach ($collections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
if (!$database->getCollection($key)->isEmpty()) {
continue;
}
Console::info(" └── Creating collection: {$collection['$id']}...");
$attributes = array_map(fn ($attr) => new Document([
'$id' => ID::custom($attr['$id']),
'type' => $attr['type'],
'size' => $attr['size'],
'required' => $attr['required'],
'signed' => $attr['signed'],
'array' => $attr['array'],
'filters' => $attr['filters'],
'default' => $attr['default'] ?? null,
'format' => $attr['format'] ?? ''
]), $collection['attributes']);
$indexes = array_map(fn ($index) => new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]), $collection['indexes']);
$database->createCollection($key, $attributes, $indexes);
}
if ($extraSetup) {
$extraSetup($database);
}
}
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
$app = new App('UTC');
@@ -67,156 +239,84 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
/** @var Group $pools */
App::setResource('pools', fn () => $pools);
// wait for database to be ready
$attempts = 0;
$max = 10;
$sleep = 1;
do {
try {
$attempts++;
$dbForConsole = $app->getResource('dbForConsole');
/** @var Utopia\Database\Database $dbForConsole */
break; // leave the do-while if successful
} catch (\Throwable $e) {
Console::warning("Database not ready. Retrying connection ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
}
} while ($attempts < $max);
Console::success('[Setup] - Server database init started...');
try {
Console::success('[Setup] - Creating database: appwrite...');
$dbForConsole->create();
} catch (\Throwable $e) {
Console::success('[Setup] - Skip: metadata table already exists');
}
if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForConsole);
$audit->setup();
}
if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
$adapter = new TimeLimit("", 0, 1, $dbForConsole);
$adapter->setup();
}
/** @var array $collections */
$collections = Config::getParam('collections', []);
$consoleCollections = $collections['console'];
foreach ($consoleCollections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
if (!$dbForConsole->getCollection($key)->isEmpty()) {
continue;
// create logs database first, `getLogsDB` is a callable.
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
// create appwrite database, `dbForPlatform` is a direct access call.
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections) {
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForPlatform);
$audit->setup();
}
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty() &&
!$dbForPlatform->exists($dbForPlatform->getDatabase(), 'bucket_1')) {
Console::info(" └── Creating default bucket...");
$dbForPlatform->createDocument('buckets', new Document([
'$id' => ID::custom('default'),
'$collection' => ID::custom('buckets'),
'name' => 'Default',
'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0),
'allowedFileExtensions' => [],
'enabled' => true,
'compression' => 'gzip',
'encryption' => true,
'antivirus' => true,
'fileSecurity' => true,
'$permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'search' => 'buckets Default',
]));
$attributes = [];
$indexes = [];
$bucket = $dbForPlatform->getDocument('buckets', 'default');
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => ID::custom($attribute['$id']),
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
'default' => $attribute['default'] ?? null,
'format' => $attribute['format'] ?? ''
]);
}
Console::info(" └── Creating files collection for default bucket...");
$files = $collections['buckets']['files'] ?? [];
if (empty($files)) {
throw new Exception('Files collection is not configured.');
}
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
$attributes = array_map(fn ($attr) => new Document([
'$id' => ID::custom($attr['$id']),
'type' => $attr['type'],
'size' => $attr['size'],
'required' => $attr['required'],
'signed' => $attr['signed'],
'array' => $attr['array'],
'filters' => $attr['filters'],
'default' => $attr['default'] ?? null,
'format' => $attr['format'] ?? ''
]), $files['attributes']);
$indexes = array_map(fn ($index) => new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
]), $files['indexes']);
$dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
}
$dbForConsole->createCollection($key, $attributes, $indexes);
}
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
Console::success('[Setup] - Creating default bucket...');
$dbForConsole->createDocument('buckets', new Document([
'$id' => ID::custom('default'),
'$collection' => ID::custom('buckets'),
'name' => 'Default',
'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
'allowedFileExtensions' => [],
'enabled' => true,
'compression' => 'gzip',
'encryption' => true,
'antivirus' => true,
'fileSecurity' => true,
'$permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'search' => 'buckets Default',
]));
$bucket = $dbForConsole->getDocument('buckets', 'default');
Console::success('[Setup] - Creating files collection for default bucket...');
$files = $collections['buckets']['files'] ?? [];
if (empty($files)) {
throw new Exception('Files collection is not configured.');
}
$attributes = [];
$indexes = [];
foreach ($files['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => ID::custom($attribute['$id']),
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
'default' => $attribute['default'] ?? null,
'format' => $attribute['format'] ?? ''
]);
}
foreach ($files['indexes'] as $index) {
$indexes[] = new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
}
});
$pools->reclaim();
Console::success('[Setup] - Server database init completed...');
});
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
// Start the task that starts fetching custom domains
$http->task([], 0);
// listen ctrl + c
Process::signal(2, function () use ($http) {
Console::log('Stop by Ctrl+C');
@@ -224,7 +324,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
});
});
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
App::setResource('swooleRequest', fn () => $swooleRequest);
App::setResource('swooleResponse', fn () => $swooleResponse);
@@ -244,6 +344,8 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
}
$app = new App('UTC');
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
$pools = $register->get('pools');
App::setResource('pools', fn () => $pools);
@@ -254,6 +356,12 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$app->run($request, $response);
} catch (\Throwable $th) {
if (
($th instanceof PDOException || $th instanceof DatabaseException)
&& DetectsLostConnections::causedByLostConnection($th)
) {
}
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$logger = $app->getResource("logger");
@@ -271,10 +379,12 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
} else {
$log->setUser(new User('guest-' . hash('sha256', $request->getIP())));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($th->getMessage());
@@ -292,8 +402,16 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$sdk = $route->getLabel("sdk", false);
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
if (!empty($sdk)) {
/** @var Appwrite\SDK\Method $sdk */
$action = $sdk->getNamespace() . '.' . $sdk->getMethodName();
}
$log->setAction($action);
$log->addTag('service', $action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
@@ -332,4 +450,59 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
}
});
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
$http->on('Task', function () use ($register, $domains) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
App::setResource('pools', fn () => $pools);
$app = new App('UTC');
/** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform');
Console::loop(function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
try {
$time = DateTime::now();
$limit = 1000;
$sum = $limit;
$latestDocument = null;
while ($sum === $limit) {
$queries = [Query::limit($limit)];
if ($latestDocument !== null) {
$queries[] = Query::cursorAfter($latestDocument);
}
if ($lastSyncUpdate != null) {
$queries[] = Query::greaterThanEqual('$updatedAt', $lastSyncUpdate);
}
$queries[] = Query::equal('resourceType', ['function']);
$results = [];
try {
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
} catch (Throwable $th) {
Console::error($th->getMessage());
}
$sum = count($results);
foreach ($results as $document) {
$domain = $document->getAttribute('domain');
if (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
continue;
}
$domains->set(md5($domain), ['value' => 1]);
}
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
}
$lastSyncUpdate = $time;
if ($sum > 0) {
Console::log("Sync domains tick: {$sum} domains were updated");
}
} catch (Throwable $th) {
Console::error($th->getMessage());
}
}, DOMAIN_SYNC_TIMER, 0, function ($error) {
Console::error($error);
});
});
$http->start();
+245 -110
View File
@@ -31,7 +31,9 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Usage;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Specification;
use Appwrite\GraphQL\Promises\Adapter\Swoole;
@@ -40,11 +42,13 @@ use Appwrite\Hooks\Hooks;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Origin;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\PubSub\Adapter\Redis as PubSub;
use Appwrite\URL\URL as AppwriteURL;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Database\PDOProxy;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Adapter\Sharding;
@@ -70,10 +74,10 @@ use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Pools\Connection as PoolConnection;
use Utopia\Pools\Group;
use Utopia\Pools\Pool;
use Utopia\Queue;
use Utopia\Queue\Connection;
use Utopia\Registry\Registry;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Backblaze;
@@ -118,9 +122,10 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4318;
const APP_VERSION_STABLE = '1.6.0';
const APP_VERSION_STABLE = '1.6.1';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -198,6 +203,7 @@ const DELETE_TYPE_TOPIC = 'topic';
const DELETE_TYPE_TARGET = 'target';
const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
const DELETE_TYPE_MAINTENANCE = 'maintenance';
// Message types
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
@@ -228,7 +234,10 @@ const API_KEY_DYNAMIC = 'dynamic';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
const METRIC_WEBHOOKS_SENT = 'webhooks.events.sent';
const METRIC_WEBHOOKS_FAILED = 'webhooks.events.failed';
const METRIC_WEBHOOK_ID_SENT = '{webhookInternalId}.webhooks.events.sent';
const METRIC_WEBHOOK_ID_FAILED = '{webhookInternalId}.webhooks.events.failed';
const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
const METRIC_MESSAGES = 'messages';
@@ -250,9 +259,17 @@ const METRIC_DOCUMENTS = 'documents';
const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage';
const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads';
const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads';
const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes';
const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes';
const METRIC_BUCKETS = 'buckets';
const METRIC_FILES = 'files';
const METRIC_FILES_STORAGE = 'files.storage';
const METRIC_FILES_TRANSFORMATIONS = 'files.transformations';
const METRIC_BUCKET_ID_FILES_TRANSFORMATIONS = '{bucketInternalId}.files.transformations';
const METRIC_FILES_IMAGES_TRANSFORMED = 'files.imagesTransformed';
const METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED = '{bucketInternalId}.files.imagesTransformed';
const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files';
const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage';
const METRIC_FUNCTIONS = 'functions';
@@ -285,6 +302,18 @@ const METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS = '{functionInternalId}.execution
const METRIC_NETWORK_REQUESTS = 'network.requests';
const METRIC_NETWORK_INBOUND = 'network.inbound';
const METRIC_NETWORK_OUTBOUND = 'network.outbound';
const METRIC_MAU = 'users.mau';
const METRIC_DAU = 'users.dau';
const METRIC_WAU = 'users.wau';
const METRIC_WEBHOOKS = 'webhooks';
const METRIC_PLATFORMS = 'platforms';
const METRIC_PROVIDERS = 'providers';
const METRIC_TOPICS = 'topics';
const METRIC_KEYS = 'keys';
const METRIC_RESOURCE_TYPE_ID_BUILDS = '{resourceType}.{resourceInternalId}.builds';
const METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE = '{resourceType}.{resourceInternalId}.builds.storage';
const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
// Resource types
@@ -718,12 +747,19 @@ Database::addFilter(
$data = \json_decode($message->getAttribute('data', []), true);
$providerType = $message->getAttribute('providerType', '');
if ($providerType === MESSAGE_TYPE_EMAIL) {
$searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
} elseif ($providerType === MESSAGE_TYPE_SMS) {
$searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
} else {
$searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
switch ($providerType) {
case MESSAGE_TYPE_EMAIL:
$searchValues[] = $data['subject'];
$searchValues[] = MESSAGE_TYPE_EMAIL;
break;
case MESSAGE_TYPE_SMS:
$searchValues[] = $data['content'];
$searchValues[] = MESSAGE_TYPE_SMS;
break;
case MESSAGE_TYPE_PUSH:
$searchValues[] = $data['title'] ?? '';
$searchValues[] = MESSAGE_TYPE_PUSH;
break;
}
$search = \implode(' ', \array_filter($searchValues));
@@ -747,7 +783,7 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
}, Database::VAR_DATETIME);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
$elements = $attribute['formatOptions']['elements'];
$elements = $attribute['formatOptions']['elements'] ?? [];
return new WhiteList($elements, true);
}, Database::VAR_STRING);
@@ -850,31 +886,43 @@ $register->set('pools', function () {
$connections = [
'console' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB),
'dsns' => $fallbackForDB,
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
],
'database' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB),
'dsns' => $fallbackForDB,
'multiple' => true,
'schemes' => ['mariadb', 'mysql'],
],
'queue' => [
'type' => 'queue',
'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis),
'logs' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
],
'publisher' => [
'type' => 'publisher',
'dsns' => $fallbackForRedis,
'multiple' => false,
'schemes' => ['redis'],
],
'consumer' => [
'type' => 'consumer',
'dsns' => $fallbackForRedis,
'multiple' => false,
'schemes' => ['redis'],
],
'pubsub' => [
'type' => 'pubsub',
'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis),
'dsns' => $fallbackForRedis,
'multiple' => false,
'schemes' => ['redis'],
],
'cache' => [
'type' => 'cache',
'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis),
'dsns' => $fallbackForRedis,
'multiple' => true,
'schemes' => ['redis'],
],
@@ -886,7 +934,7 @@ $register->set('pools', function () {
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
if ($multiprocessing) {
$workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
} else {
$workerCount = 1;
}
@@ -938,7 +986,7 @@ $register->set('pools', function () {
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_PERSISTENT => false, // We manage our own pool
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true
@@ -946,12 +994,12 @@ $register->set('pools', function () {
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new Redis();
$redis = new \Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
if ($dsnPass) {
$redis->auth($dsnPass);
}
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
},
@@ -969,28 +1017,26 @@ $register->set('pools', function () {
};
$adapter->setDatabase($dsn->getPath());
break;
return $adapter;
case 'pubsub':
$adapter = $resource();
break;
case 'queue':
$adapter = match ($dsn->getScheme()) {
'redis' => new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()),
return match ($dsn->getScheme()) {
'redis' => new PubSub($resource()),
default => null
};
case 'publisher':
case 'consumer':
return match ($dsn->getScheme()) {
'redis' => new Queue\Broker\Redis(new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort())),
default => null
};
break;
case 'cache':
$adapter = match ($dsn->getScheme()) {
return match ($dsn->getScheme()) {
'redis' => new RedisCache($resource()),
default => null
};
break;
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
}
return $adapter;
});
$group->add($pool);
@@ -1113,42 +1159,54 @@ App::setResource('localeCodes', function () {
});
// Queues
App::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
App::setResource('publisher', function (Group $pools) {
return $pools->get('publisher')->pop()->getResource();
}, ['pools']);
App::setResource('queueForMessaging', function (Connection $queue) {
return new Messaging($queue);
}, ['queue']);
App::setResource('queueForMails', function (Connection $queue) {
return new Mail($queue);
}, ['queue']);
App::setResource('queueForBuilds', function (Connection $queue) {
return new Build($queue);
}, ['queue']);
App::setResource('queueForDatabase', function (Connection $queue) {
return new EventDatabase($queue);
}, ['queue']);
App::setResource('queueForDeletes', function (Connection $queue) {
return new Delete($queue);
}, ['queue']);
App::setResource('queueForEvents', function (Connection $queue) {
return new Event($queue);
}, ['queue']);
App::setResource('queueForAudits', function (Connection $queue) {
return new Audit($queue);
}, ['queue']);
App::setResource('queueForFunctions', function (Connection $queue) {
return new Func($queue);
}, ['queue']);
App::setResource('queueForUsage', function (Connection $queue) {
return new Usage($queue);
}, ['queue']);
App::setResource('queueForCertificates', function (Connection $queue) {
return new Certificate($queue);
}, ['queue']);
App::setResource('queueForMigrations', function (Connection $queue) {
return new Migration($queue);
}, ['queue']);
App::setResource('consumer', function (Group $pools) {
return $pools->get('consumer')->pop()->getResource();
}, ['pools']);
App::setResource('queueForMessaging', function (Queue\Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
App::setResource('queueForMails', function (Queue\Publisher $publisher) {
return new Mail($publisher);
}, ['publisher']);
App::setResource('queueForBuilds', function (Queue\Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
App::setResource('queueForDatabase', function (Queue\Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
App::setResource('queueForDeletes', function (Queue\Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
App::setResource('queueForEvents', function (Queue\Publisher $publisher) {
return new Event($publisher);
}, ['publisher']);
App::setResource('queueForWebhooks', function (Queue\Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
App::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
App::setResource('queueForStatsUsage', function (Queue\Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
App::setResource('queueForAudits', function (Queue\Publisher $publisher) {
return new Audit($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']);
App::setResource('queueForMigrations', function (Queue\Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
App::setResource('clients', function ($request, $console, $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => ID::custom('platforms'),
@@ -1201,12 +1259,12 @@ App::setResource('clients', function ($request, $console, $project) {
return \array_unique($clients);
}, ['request', 'console', 'project']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) {
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var string $mode */
Authorization::setDefaultStatus(true);
@@ -1255,13 +1313,13 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$user = new Document([]);
} else {
if ($project->getId() === 'console') {
$user = $dbForConsole->getDocument('users', Auth::$unique);
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} else {
$user = $dbForProject->getDocument('users', Auth::$unique);
}
}
} else {
$user = $dbForConsole->getDocument('users', Auth::$unique);
$user = $dbForPlatform->getDocument('users', Auth::$unique);
}
if (
@@ -1304,14 +1362,14 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
}
$dbForProject->setMetadata('user', $user->getId());
$dbForConsole->setMetadata('user', $user->getId());
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
App::setResource('project', function ($dbForConsole, $request, $console) {
App::setResource('project', function ($dbForPlatform, $request, $console) {
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var Utopia\Database\Document $console */
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
@@ -1320,10 +1378,10 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
return $console;
}
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
return $project;
}, ['dbForConsole', 'request', 'console']);
}, ['dbForPlatform', 'request', 'console']);
App::setResource('session', function (Document $user) {
if ($user->isEmpty()) {
@@ -1388,9 +1446,11 @@ App::setResource('console', function () {
]);
}, []);
App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project) {
App::setResource('connectionForProject', function (Group $pools, Document $project) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
return $pools
->get('console')
->pop();
}
try {
@@ -1400,12 +1460,24 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$dbAdapter = $pools
return $pools
->get($dsn->getHost())
->pop()
->getResource();
->pop();
}, ['pools', 'project']);
$database = new Database($dbAdapter, $cache);
App::setResource('dbForProject', function (Group $pools, PoolConnection $connectionForProject, Database $dbForPlatform, Cache $cache, Document $project) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$database = new Database($connectionForProject->getResource(), $cache);
$database
->setMetadata('host', \gethostname())
@@ -1413,14 +1485,9 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -1433,9 +1500,9 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
}
return $database;
}, ['pools', 'dbForConsole', 'cache', 'project']);
}, ['pools', 'connectionForProject', 'dbForPlatform', 'cache', 'project']);
App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$dbAdapter = $pools
->get('console')
->pop()
@@ -1453,12 +1520,12 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
return $database;
}, ['pools', 'cache']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, PoolConnection $connectionForProject, $cache) {
$databases = [];
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
return function (Document $project) use ($pools, $connectionForProject, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
return $dbForPlatform;
}
try {
@@ -1475,7 +1542,9 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -1494,10 +1563,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
return $database;
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$dbAdapter = $connectionForProject->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$dsn->getHost()] = $database;
@@ -1505,7 +1571,40 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
return $database;
};
}, ['pools', 'dbForConsole', 'cache']);
}, ['pools', 'dbForPlatform', 'connectionForProject', 'cache']);
App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
return $database;
}
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$database
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
}
return $database;
};
}, ['pools', 'cache']);
App::setResource('cache', function (Group $pools) {
$list = Config::getParam('pools-cache', []);
@@ -1522,6 +1621,27 @@ App::setResource('cache', function (Group $pools) {
return new Cache(new Sharding($adapters));
}, ['pools']);
App::setResource('redis', function () {
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
$pass = System::getEnv('_APP_REDIS_PASS', '');
$redis = new \Redis();
@$redis->pconnect($host, (int)$port);
if ($pass) {
$redis->auth($pass);
}
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
});
App::setResource('timelimit', function (\Redis $redis) {
return function (string $key, int $limit, int $time) use ($redis) {
return new TimeLimitRedis($key, $limit, $time, $redis);
};
}, ['redis']);
App::setResource('deviceForLocal', function () {
return new Local();
});
@@ -1787,7 +1907,11 @@ App::setResource('plan', function (array $plan = []) {
return [];
});
App::setResource('team', function (Document $project, Database $dbForConsole, App $utopia, Request $request) {
App::setResource('smsRates', function () {
return [];
});
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
$teamInternalId = '';
if ($project->getId() !== 'console') {
$teamInternalId = $project->getAttribute('teamInternalId', '');
@@ -1797,25 +1921,36 @@ App::setResource('team', function (Document $project, Database $dbForConsole, Ap
if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI();
$pid = explode('/', $uri)[3];
$p = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $pid));
$p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid));
$teamInternalId = $p->getAttribute('teamInternalId', '');
} elseif ($path === '/v1/projects') {
$teamId = $request->getParam('teamId', '');
$team = Authorization::skip(fn () => $dbForConsole->getDocument('teams', $teamId));
$team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
return $team;
}
}
$team = Authorization::skip(function () use ($dbForConsole, $teamInternalId) {
return $dbForConsole->findOne('teams', [
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
return $dbForPlatform->findOne('teams', [
Query::equal('$internalId', [$teamInternalId]),
]);
});
return $team;
}, ['project', 'dbForConsole', 'utopia', 'request']);
}, ['project', 'dbForPlatform', 'utopia', 'request']);
App::setResource(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
App::setResource('previewHostname', function (Request $request) {
if (App::isDevelopment()) {
$host = $request->getQuery('appwrite-hostname') ?? '';
if (!empty($host)) {
return $host;
}
}
return '';
}, ['request']);
+67 -18
View File
@@ -13,7 +13,7 @@ use Swoole\Runtime;
use Swoole\Table;
use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -29,6 +29,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\System\System;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
use Utopia\WebSocket\Adapter;
use Utopia\WebSocket\Server;
@@ -92,7 +93,9 @@ if (!function_exists('getProjectDB')) {
$database = new Database($adapter, getCache());
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -135,6 +138,32 @@ if (!function_exists('getCache')) {
}
}
// Allows overriding
if (!function_exists('getRedis')) {
function getRedis(): \Redis
{
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
$pass = System::getEnv('_APP_REDIS_PASS', '');
$redis = new \Redis();
@$redis->pconnect($host, (int)$port);
if ($pass) {
$redis->auth($pass);
}
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
}
}
if (!function_exists('getTimelimit')) {
function getTimelimit(): TimeLimitRedis
{
return new TimeLimitRedis("", 0, 1, getRedis());
}
}
if (!function_exists('getRealtime')) {
function getRealtime(): Realtime
{
@@ -142,6 +171,13 @@ if (!function_exists('getRealtime')) {
}
}
if (!function_exists('getTelemetry')) {
function getTelemetry(int $workerId): Utopia\Telemetry\Adapter
{
return new NoTelemetry();
}
}
$realtime = getRealtime();
/**
@@ -157,7 +193,7 @@ $stats->create();
$containerId = uniqid();
$statsDocument = null;
$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$workerNumber = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$adapter = new Adapter\Swoole(port: System::getEnv('PORT', 80));
$adapter
@@ -174,7 +210,7 @@ $logError = function (Throwable $error, string $action) use ($register) {
$log = new Log();
$log->setNamespace("realtime");
$log->setServer(gethostname());
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
@@ -274,6 +310,12 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
Console::success('Worker ' . $workerId . ' started successfully');
$telemetry = getTelemetry($workerId);
$register->set('telemetry', fn () => $telemetry);
$register->set('telemetry.connectionCounter', fn () => $telemetry->createUpDownCounter('realtime.server.open_connections'));
$register->set('telemetry.connectionCreatedCounter', fn () => $telemetry->createCounter('realtime.server.connection.created'));
$register->set('telemetry.messageSentCounter', fn () => $telemetry->createCounter('realtime.server.message.sent'));
$attempts = 0;
$start = time();
@@ -365,17 +407,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
}
$start = time();
$redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
if ($redis->ping(true)) {
/** @var \Appwrite\PubSub\Adapter $pubsub */
$pubsub = $register->get('pools')->get('pubsub')->pop()->getResource();
if ($pubsub->ping(true)) {
$attempts = 0;
Console::success('Pub/sub connection established (worker: ' . $workerId . ')');
} else {
Console::error('Pub/sub failed (worker: ' . $workerId . ')');
}
$redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) {
$pubsub->subscribe(['realtime'], function (mixed $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) {
$event = json_decode($payload, true);
if ($event['permissionsChanged'] && isset($event['userId'])) {
@@ -417,6 +458,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
);
if (($num = count($receivers)) > 0) {
$register->get('telemetry.messageSentCounter')->add($num);
$stats->incr($event['project'], 'messages', $num);
}
});
@@ -465,7 +507,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
$dbForProject = getProjectDB($project);
$timelimit = $app->getResource('timelimit');
$console = $app->getResource('console'); /** @var Document $console */
$user = $app->getResource('user'); /** @var Document $user */
@@ -474,12 +516,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
*
* Abuse limits are connecting 128 times per minute and ip address.
*/
$timeLimit = new TimeLimit('url:{url},ip:{ip}', 128, 60, $dbForProject);
$timeLimit
$timelimit = $timelimit('url:{url},ip:{ip}', 128, 60);
$timelimit
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getURI());
$abuse = new Abuse($timeLimit);
$abuse = new Abuse($timelimit);
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
@@ -520,6 +562,9 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
]
]));
$register->get('telemetry.connectionCounter')->add(1);
$register->get('telemetry.connectionCreatedCounter')->add(1);
$stats->set($project->getId(), [
'projectId' => $project->getId(),
'teamId' => $project->getAttribute('teamId')
@@ -574,7 +619,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
*
* Abuse limits are sending 32 times per minute and connection.
*/
$timeLimit = new TimeLimit('url:{url},connection:{connection}', 32, 60, $database);
$timeLimit = getTimelimit('url:{url},connection:{connection}', 32, 60);
$timeLimit
->setParam('{connection}', $connection)
@@ -593,9 +638,12 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
}
switch ($message['type']) {
/**
* This type is used to authenticate.
*/
case 'ping':
$server->send([$connection], json_encode([
'type' => 'pong'
]));
break;
case 'authentication':
if (!array_key_exists('session', $message['data'])) {
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
@@ -653,9 +701,10 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
}
});
$server->onClose(function (int $connection) use ($realtime, $stats) {
$server->onClose(function (int $connection) use ($realtime, $stats, $register) {
if (array_key_exists($connection, $realtime->connections)) {
$stats->decr($realtime->connections[$connection]['projectId'], 'connectionsTotal');
$register->get('telemetry.connectionCounter')->add(-1);
}
$realtime->unsubscribe($connection);
+67 -7
View File
@@ -73,6 +73,7 @@ $image = $this->getParam('image', '');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_LOCALE
- _APP_COMPRESSION_MIN_SIZE_BYTES
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_SESSION_ALERTS
@@ -166,7 +167,7 @@ $image = $this->getParam('image', '');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:5.0.12
image: <?php echo $organization; ?>/console:5.2.27
restart: unless-stopped
networks:
- appwrite
@@ -648,10 +649,69 @@ $image = $this->getParam('image', '');
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
appwrite-worker-usage:
appwrite-task-stats-resources:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-usage
container_name: appwrite-worker-usage
container_name: appwrite-task-stats-resources
entrypoint: stats-resources
<<: *x-logging
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
- _APP_STATS_RESOURCES_INTERVAL
appwrite-worker-stats-resources:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-stats-resources
container_name: appwrite-worker-stats-resources
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_STATS_RESOURCES_INTERVAL
appwrite-worker-stats-usage:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-stats-usage
container_name: appwrite-worker-stats-usage
<<: *x-logging
restart: unless-stopped
networks:
@@ -676,11 +736,11 @@ $image = $this->getParam('image', '');
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-worker-usage-dump:
appwrite-worker-stats-usage-dump:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-usage-dump
entrypoint: worker-stats-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
container_name: appwrite-worker-stats-usage-dump
restart: unless-stopped
networks:
- appwrite
+214 -72
View File
@@ -2,6 +2,7 @@
require_once __DIR__ . '/init.php';
use Appwrite\Certificates\LetsEncrypt;
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
@@ -12,11 +13,16 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\StatsUsageDump;
/** remove */
use Appwrite\Event\Usage;
use Appwrite\Event\UsageDump;
/** /remove */
use Appwrite\Platform\Appwrite;
use Swoole\Database\DetectsLostConnections;
use Swoole\Runtime;
use Utopia\App;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
@@ -24,14 +30,16 @@ use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception as DatabaseException;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
use Utopia\Pools\Connection as PoolConnection;
use Utopia\Pools\Group;
use Utopia\Queue\Connection;
use Utopia\Queue\Message;
use Utopia\Queue\Publisher;
use Utopia\Queue\Server;
use Utopia\Registry\Registry;
use Utopia\System\System;
@@ -41,7 +49,7 @@ Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
Server::setResource('register', fn () => $register);
Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
$pools = $register->get('pools');
$database = $pools
->get('console')
@@ -54,7 +62,7 @@ Server::setResource('dbForConsole', function (Cache $cache, Registry $register)
return $adapter;
}, ['cache', 'register']);
Server::setResource('project', function (Message $message, Database $dbForConsole) {
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
@@ -62,16 +70,16 @@ Server::setResource('project', function (Message $message, Database $dbForConsol
return $project;
}
return $dbForConsole->getDocument('projects', $project->getId());
}, ['message', 'dbForConsole']);
return $dbForPlatform->getDocument('projects', $project->getId());
}, ['message', 'dbForPlatform']);
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) {
Server::setResource('connectionForProject', function (Group $pools, Document $project) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
return $pools
->get('console')
->pop();
}
$pools = $register->get('pools');
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
@@ -79,12 +87,17 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$adapter = $pools
return $pools
->get($dsn->getHost())
->pop()
->getResource();
->pop();
}, ['pools', 'project']);
$database = new Database($adapter, $cache);
Server::setResource('dbForProject', function (PoolConnection $connectionForProject, Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
$database = new Database($connectionForProject->getResource(), $cache);
try {
$dsn = new DSN($project->getAttribute('database'));
@@ -93,7 +106,9 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -106,14 +121,14 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
}
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForConsole']);
}, ['connectionForProject', 'cache', 'register', 'message', 'project', 'dbForPlatform']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
Server::setResource('getProjectDB', function (Group $pools, PoolConnection $connectionForProject, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases): Database {
return function (Document $project) use ($pools, $connectionForProject, $dbForPlatform, $cache, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
return $dbForPlatform;
}
try {
@@ -126,7 +141,9 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -141,16 +158,13 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
return $database;
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$database = new Database($connectionForProject->getResource(), $cache);
$databases[$dsn->getHost()] = $database;
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -164,10 +178,43 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
return $database;
};
}, ['pools', 'dbForConsole', 'cache']);
}, ['pools', 'connectionForProject', 'dbForPlatform', 'cache']);
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
return $database;
}
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$database
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
}
return $database;
};
}, ['pools', 'cache']);
Server::setResource('abuseRetention', function () {
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400));
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400);
});
Server::setResource('auditRetention', function () {
@@ -194,59 +241,92 @@ Server::setResource('cache', function (Registry $register) {
return new Cache(new Sharding($adapters));
}, ['register']);
Server::setResource('redis', function () {
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
$pass = System::getEnv('_APP_REDIS_PASS', '');
$redis = new \Redis();
@$redis->pconnect($host, (int)$port);
if ($pass) {
$redis->auth($pass);
}
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
});
Server::setResource('timelimit', function (\Redis $redis) {
return function (string $key, int $limit, int $time) use ($redis) {
return new TimeLimitRedis($key, $limit, $time, $redis);
};
}, ['redis']);
Server::setResource('log', fn () => new Log());
Server::setResource('queueForUsage', function (Connection $queue) {
return new Usage($queue);
}, ['queue']);
Server::setResource('queueForUsageDump', function (Connection $queue) {
return new UsageDump($queue);
}, ['queue']);
Server::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
Server::setResource('publisher', function (Group $pools) {
return $pools->get('publisher')->pop()->getResource();
}, ['pools']);
Server::setResource('queueForDatabase', function (Connection $queue) {
return new EventDatabase($queue);
}, ['queue']);
Server::setResource('consumer', function (Group $pools) {
return $pools->get('consumer')->pop()->getResource();
}, ['pools']);
Server::setResource('queueForMessaging', function (Connection $queue) {
return new Messaging($queue);
}, ['queue']);
Server::setResource('queueForUsage', function (Publisher $publisher) {
return new Usage($publisher);
}, ['publisher']);
Server::setResource('queueForMails', function (Connection $queue) {
return new Mail($queue);
}, ['queue']);
Server::setResource('queueForUsageDump', function (Publisher $publisher) {
return new UsageDump($publisher);
}, ['publisher']);
Server::setResource('queueForBuilds', function (Connection $queue) {
return new Build($queue);
}, ['queue']);
Server::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
Server::setResource('queueForDeletes', function (Connection $queue) {
return new Delete($queue);
}, ['queue']);
Server::setResource('queueForStatsUsageDump', function (Publisher $publisher) {
return new StatsUsageDump($publisher);
}, ['publisher']);
Server::setResource('queueForEvents', function (Connection $queue) {
return new Event($queue);
}, ['queue']);
Server::setResource('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
Server::setResource('queueForAudits', function (Connection $queue) {
return new Audit($queue);
}, ['queue']);
Server::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
Server::setResource('queueForFunctions', function (Connection $queue) {
return new Func($queue);
}, ['queue']);
Server::setResource('queueForMails', function (Publisher $publisher) {
return new Mail($publisher);
}, ['publisher']);
Server::setResource('queueForCertificates', function (Connection $queue) {
return new Certificate($queue);
}, ['queue']);
Server::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
Server::setResource('queueForMigrations', function (Connection $queue) {
return new Migration($queue);
}, ['queue']);
Server::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
Server::setResource('queueForEvents', function (Publisher $publisher) {
return new Event($publisher);
}, ['publisher']);
Server::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
}, ['publisher']);
Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
Server::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
Server::setResource('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
Server::setResource('logger', function (Registry $register) {
return $register->get('logger');
@@ -277,6 +357,59 @@ Server::setResource(
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
Server::setResource('certificates', function () {
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
if (empty($email)) {
throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue a LetsEncrypt SSL certificate.');
}
return new LetsEncrypt($email);
});
Server::setResource('logError', function (Registry $register, Document $project) {
return function (Throwable $error, string $namespace, string $action, ?array $extras) use ($register, $project) {
$logger = $register->get('logger');
if ($logger) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace($namespace);
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', get_class($error));
$log->addTag('projectId', $project->getId() ?? '');
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
foreach ($extras as $key => $value) {
$log->addExtra($key, $value);
}
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
}
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
};
}, ['register', 'project']);
$pools = $register->get('pools');
$platform = new Appwrite();
@@ -305,7 +438,7 @@ try {
*/
$platform->init(Service::TYPE_WORKER, [
'workersNum' => System::getEnv('_APP_WORKERS_NUM', 1),
'connection' => $pools->get('queue')->pop()->getResource(),
'connection' => $pools->get('consumer')->pop()->getResource(),
'workerName' => strtolower($workerName) ?? null,
'queueName' => $queueName
]);
@@ -329,13 +462,22 @@ $worker
->inject('log')
->inject('pools')
->inject('project')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) {
->inject('connectionForProject')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, PoolConnection $connectionForProject) use ($queueName) {
if (
($error instanceof PDOException || $error instanceof DatabaseException)
&& DetectsLostConnections::causedByLostConnection($error)
) {
// Mark connection as unhealthy, it will be recycled on next reclaim.
$connectionForProject->setHealthy(false);
}
$pools->reclaim();
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
$log->setNamespace("appwrite-worker");
$log->setServer(\gethostname());
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php stats-resources $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php stats-resources $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php stats-usage $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php stats-usage-dump $@
+14 -9
View File
@@ -45,31 +45,32 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.16.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.43.0",
"utopia-php/abuse": "0.50.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.43.0",
"utopia-php/cache": "0.10.*",
"utopia-php/audit": "0.51.*",
"utopia-php/cache": "0.11.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.53.9",
"utopia-php/database": "0.59.0",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.2.*",
"utopia-php/fetch": "0.3.*",
"utopia-php/image": "0.7.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.12.*",
"utopia-php/messaging": "0.14.*",
"utopia-php/migration": "0.6.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/platform": "0.7.3",
"utopia-php/pools": "0.5.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.7.*",
"utopia-php/queue": "0.8.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
"utopia-php/system": "0.9.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/vcs": "0.8.*",
"utopia-php/websocket": "0.1.*",
"matomo/device-detector": "6.1.*",
@@ -83,7 +84,7 @@
},
"require-dev": {
"ext-fileinfo": "*",
"appwrite/sdk-generator": "0.39.*",
"appwrite/sdk-generator": "0.39.32",
"phpunit/phpunit": "9.5.20",
"swoole/ide-helper": "5.1.2",
"textalk/websocket": "1.5.7",
@@ -96,6 +97,10 @@
"config": {
"platform": {
"php": "8.3"
},
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": false
}
}
}
Generated
+2314 -572
View File
File diff suppressed because it is too large Load Diff
+79 -10
View File
@@ -96,6 +96,7 @@ services:
- _APP_EDITION
- _APP_WORKER_PER_CORE
- _APP_LOCALE
- _APP_COMPRESSION_MIN_SIZE_BYTES
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
- _APP_CONSOLE_SESSION_ALERTS
@@ -192,11 +193,14 @@ services:
- _APP_EXPERIMENT_LOGGING_PROVIDER
- _APP_EXPERIMENT_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_SHARED_TABLES_V1
- _APP_DATABASE_SHARED_NAMESPACE
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:5.0.12
image: appwrite/console:5.2.27
restart: unless-stopped
networks:
- appwrite
@@ -381,6 +385,8 @@ services:
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_SHARED_TABLES_V1
- _APP_EMAIL_CERTIFICATES
appwrite-worker-databases:
entrypoint: worker-databases
@@ -716,10 +722,72 @@ services:
- _APP_MAINTENANCE_DELAY
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-usage:
entrypoint: worker-usage
appwrite-task-stats-resources:
container_name: appwrite-task-stats-resources
entrypoint: stats-resources
<<: *x-logging
container_name: appwrite-worker-usage
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
- _APP_STATS_RESOURCES_INTERVAL
appwrite-worker-stats-resources:
entrypoint: worker-stats-resources
<<: *x-logging
container_name: appwrite-worker-stats-resources
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-stats-usage:
entrypoint: worker-stats-usage
<<: *x-logging
container_name: appwrite-worker-stats-usage
image: appwrite-dev
networks:
- appwrite
@@ -747,10 +815,10 @@ services:
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-usage-dump:
entrypoint: worker-usage-dump
appwrite-worker-stats-usage-dump:
entrypoint: worker-stats-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
container_name: appwrite-worker-stats-usage-dump
image: appwrite-dev
networks:
- appwrite
@@ -777,7 +845,8 @@ services:
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_DATABASE_SHARED_TABLES
- _APP_STATS_USAGE_DUAL_WRITING_DBS
appwrite-task-scheduler-functions:
entrypoint: schedule-functions
<<: *x-logging
@@ -863,7 +932,7 @@ services:
appwrite-assistant:
container_name: appwrite-assistant
image: appwrite/assistant:0.5.0
image: appwrite/assistant:0.7.0
networks:
- appwrite
environment:
@@ -1053,4 +1122,4 @@ volumes:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-config:
appwrite-config:
@@ -12,5 +12,6 @@ mutation {
providerId
providerType
identifier
expired
}
}
@@ -33,6 +33,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -28,6 +28,7 @@ query {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -31,6 +31,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -30,6 +30,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -31,6 +31,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -30,6 +30,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -31,6 +31,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -31,6 +31,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -30,6 +30,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -11,5 +11,6 @@ mutation {
providerId
providerType
identifier
expired
}
}
@@ -28,6 +28,7 @@ mutation {
providerId
providerType
identifier
expired
}
accessedAt
}
@@ -17,6 +17,7 @@ mutation {
providerId
providerType
identifier
expired
}
userId
userName
@@ -3,4 +3,5 @@ appwrite databases updateBooleanAttribute \
--collectionId <COLLECTION_ID> \
--key '' \
--required false \
--default false
--default false \
@@ -3,4 +3,5 @@ appwrite databases updateDatetimeAttribute \
--collectionId <COLLECTION_ID> \
--key '' \
--required false \
--default ''
--default '' \
@@ -3,4 +3,5 @@ appwrite databases updateEmailAttribute \
--collectionId <COLLECTION_ID> \
--key '' \
--required false \
--default email@example.com
--default email@example.com \
@@ -4,4 +4,5 @@ appwrite databases updateEnumAttribute \
--key '' \
--elements one two three \
--required false \
--default <DEFAULT>
--default <DEFAULT> \
@@ -5,4 +5,5 @@ appwrite databases updateFloatAttribute \
--required false \
--min null \
--max null \
--default null
--default null \
@@ -5,4 +5,5 @@ appwrite databases updateIntegerAttribute \
--required false \
--min null \
--max null \
--default null
--default null \
@@ -3,4 +3,5 @@ appwrite databases updateIpAttribute \
--collectionId <COLLECTION_ID> \
--key '' \
--required false \
--default ''
--default '' \
@@ -3,3 +3,4 @@ appwrite databases updateRelationshipAttribute \
--collectionId <COLLECTION_ID> \
--key '' \
@@ -3,4 +3,6 @@ appwrite databases updateStringAttribute \
--collectionId <COLLECTION_ID> \
--key '' \
--required false \
--default <DEFAULT>
--default <DEFAULT> \
@@ -3,4 +3,5 @@ appwrite databases updateUrlAttribute \
--collectionId <COLLECTION_ID> \
--key '' \
--required false \
--default https://example.com
--default https://example.com \
@@ -1,7 +1,10 @@
appwrite messaging createPush \
--messageId <MESSAGE_ID> \
--title <TITLE> \
--body <BODY> \
@@ -15,3 +15,6 @@ appwrite messaging updatePush \
@@ -0,0 +1,5 @@
appwrite projects updateMembershipsPrivacy \
--projectId <PROJECT_ID> \
--userName false \
--userEmail false \
--mfa false
@@ -12,7 +12,7 @@ const result = await databases.updateStringAttribute(
'', // key
false, // required
'<DEFAULT>', // default
null, // size (optional)
1, // size (optional)
'' // newKey (optional)
);
@@ -1,4 +1,4 @@
import { Client, Messaging } from "@appwrite.io/console";
import { Client, Messaging, MessagePriority } from "@appwrite.io/console";
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
@@ -8,8 +8,8 @@ const messaging = new Messaging(client);
const result = await messaging.createPush(
'<MESSAGE_ID>', // messageId
'<TITLE>', // title
'<BODY>', // body
'<TITLE>', // title (optional)
'<BODY>', // body (optional)
[], // topics (optional)
[], // users (optional)
[], // targets (optional)
@@ -20,9 +20,12 @@ const result = await messaging.createPush(
'<SOUND>', // sound (optional)
'<COLOR>', // color (optional)
'<TAG>', // tag (optional)
'<BADGE>', // badge (optional)
null, // badge (optional)
false, // draft (optional)
'' // scheduledAt (optional)
'', // scheduledAt (optional)
false, // contentAvailable (optional)
false, // critical (optional)
MessagePriority.Normal // priority (optional)
);
console.log(result);
@@ -1,4 +1,4 @@
import { Client, Messaging } from "@appwrite.io/console";
import { Client, Messaging, MessagePriority } from "@appwrite.io/console";
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
@@ -22,7 +22,10 @@ const result = await messaging.updatePush(
'<TAG>', // tag (optional)
null, // badge (optional)
false, // draft (optional)
'' // scheduledAt (optional)
'', // scheduledAt (optional)
false, // contentAvailable (optional)
false, // critical (optional)
MessagePriority.Normal // priority (optional)
);
console.log(result);
@@ -0,0 +1,16 @@
import { Client, Projects } from "@appwrite.io/console";
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
const projects = new Projects(client);
const result = await projects.updateMembershipsPrivacy(
'<PROJECT_ID>', // projectId
false, // userName
false, // userEmail
false // mfa
);
console.log(result);
@@ -13,6 +13,6 @@ AttributeString result = await databases.updateStringAttribute(
key: '',
xrequired: false,
xdefault: '<DEFAULT>',
size: 0, // (optional)
size: 1, // (optional)
newKey: '', // (optional)
);

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