Compare commits

...
Author SHA1 Message Date
Hemachandar a369d49b2c const 2026-02-10 20:49:49 +05:30
Hemachandar 22691bee66 Fix error file path in GitHub APIs 2026-02-10 20:35:39 +05:30
Matej BačoandGitHub 51f036491a Merge pull request #11278 from appwrite/feat-list-api-keys-queries
Feat: queries param on list api keys
2026-02-10 12:47:00 +01:00
Matej Bačo 4df0934023 Comment fix 2026-02-10 12:01:38 +01:00
Luke B. SilverandGitHub 5e974713b7 Merge pull request #11281 from appwrite/feat/worker-executions
feat: worker executions
2026-02-10 10:56:30 +00:00
Matej Bačo e2071bd5dd add backwards compatibility 2026-02-10 11:48:29 +01:00
Matej Bačo 0621a32aa6 Fix tests; speed up tests; fix 5xx error 2026-02-10 11:43:22 +01:00
Matej Bačo e1cbbe3943 Merge branch '1.8.x' into feat-list-api-keys-queries 2026-02-10 10:46:56 +01:00
Matej BačoandGitHub 226e36db95 Merge pull request #11277 from appwrite/feat-api-key-custom-id
Feat: custom ID for API keys
2026-02-10 10:46:00 +01:00
Matej BačoandGitHub faedf49488 Merge pull request #11276 from appwrite/fix-rule-oauth
Fix: rule oauth flow
2026-02-10 10:27:25 +01:00
loks0n b432918385 feat: worker executions 2026-02-09 20:20:44 +00:00
Matej Bačo a87263a571 Fix failing test 2026-02-09 20:07:33 +01:00
Matej Bačo dafa97879c AI review fixes 2026-02-09 17:12:30 +01:00
Matej Bačo a263afeff1 AI quality fixes 2026-02-09 17:10:00 +01:00
Matej Bačo 29915ddd3b Manual fixes of agent code 2026-02-09 16:58:44 +01:00
Matej Bačo c0f5fa90cb Fix AI review 2026-02-09 16:53:18 +01:00
Matej Bačo e666dc9504 AI review fixes 2026-02-09 16:42:14 +01:00
Matej Bačo 3dc69ba62a Revert unwanted push 2026-02-09 15:55:49 +01:00
Matej Bačo 7bf5f2d360 Fix bug 5xx error 2026-02-09 15:55:35 +01:00
Matej Bačo 6df5556473 Implement queries param to list api keys 2026-02-09 14:58:36 +01:00
Matej Bačo 96e85c0bab AI pr review 2026-02-09 14:35:47 +01:00
Matej Bačo 40ab50ec9d formatting fix 2026-02-09 14:34:52 +01:00
Matej Bačo 9b762dde40 formatting fix 2026-02-09 14:34:36 +01:00
Matej Bačo 615aff0714 Allow custom ID for API keys 2026-02-09 14:34:05 +01:00
Matej BačoandGitHub 994850c5a6 Merge pull request #11275 from appwrite/fix-vcs-template-flow
Fix VCS template flow
2026-02-09 14:00:39 +01:00
Matej Bačo 525b929e54 Fix implementation, add tests 2026-02-09 13:57:19 +01:00
Matej Bačo 074ffad826 Improve origin unit tests 2026-02-09 13:46:15 +01:00
Matej Bačo 36c87d109a Fix rule oauth flow 2026-02-09 13:45:18 +01:00
Matej Bačo e09dd98f36 Fix site vcs deployment bug too 2026-02-09 13:30:46 +01:00
Matej Bačo 96e1221a5f Fix implementation 2026-02-09 13:27:31 +01:00
Matej Bačo e13f4c8545 Fix VCS template flow 2026-02-09 13:26:33 +01:00
HemachandarandGitHub 353b7f2a49 Move VCS auth & callback APIs to Modules (#11274)
* Move VCS auth & callback APIs to Modules

* lint

* rename
2026-02-09 15:55:37 +05:30
HemachandarandGitHub f7ab8e46d3 Improve certificate dns failure logs (#11268)
* Better logs for DNS failures

* some more

* appwrite exception

* type

* refactor

* tiny

* better logs
2026-02-09 15:45:41 +05:30
Chirag AggarwalandGitHub bec6725900 Merge pull request #11273 from appwrite/release-cli-13.3.1
chore: release cli 13.3.1
2026-02-09 13:59:31 +05:30
Chirag Aggarwal 494ce5fc6a chore: release cli 13.3.1 2026-02-09 13:14:28 +05:30
Matej BačoandGitHub bae7b92dd9 Merge pull request #11269 from appwrite/fix-oauth-for-rules
Fix redirect url approval for oauth flow
2026-02-08 15:08:30 +01:00
Matej Bačo 801707c407 Linter fix 2026-02-08 14:47:47 +01:00
Matej Bačo 94581adfcb Improve dev key tests 2026-02-08 14:44:21 +01:00
Matej Bačo fd323feae8 Simplify diff 2026-02-08 14:37:35 +01:00
Matej Bačo 3a0dc60a4c Add test; fix implementation; pr reviews 2026-02-08 14:36:34 +01:00
Matej Bačo bed46a6bef Fix redirect url approval for oauth flow 2026-02-08 14:12:23 +01:00
Chirag AggarwalandGitHub ba5273872b Merge pull request #11267 from appwrite/fix/disable-execution-writes-nyc-api
fix: disable execution document writes for nyc region in API
2026-02-07 21:45:07 +05:30
Chirag Aggarwal f1639e0b67 fix: disable execution document writes for nyc region in API
Temporarily skip execution createDocument calls in the Executions
Create handler when _APP_REGION is set to 'nyc' to reduce database
load. Marked with TODO comments for removal.
2026-02-07 21:42:10 +05:30
Chirag AggarwalandGitHub e8fe9222b2 Merge pull request #11266 from appwrite/fix/disable-execution-writes-nyc
fix: disable execution document writes for nyc region
2026-02-07 21:20:58 +05:30
Chirag Aggarwal a520a67da8 fix: disable execution document writes for nyc region
Temporarily skip all execution createDocument and updateDocument calls
in the Functions worker when _APP_REGION is set to 'nyc' to reduce
database load. Marked with TODO comments for removal.
2026-02-07 21:19:00 +05:30
Luke B. SilverandGitHub 9c03c5fab3 Merge pull request #11265 from appwrite/chore/upgrade-traefik
chore: upgrade traefik
2026-02-06 20:14:38 +00:00
loks0n d864a61f93 chore: upgrade traefik 2026-02-06 19:16:37 +00:00
Chirag AggarwalandGitHub fb1cbdca6c Merge pull request #11263 from appwrite/upgrade-spomky-labs/otphp
chore: upgrade spomky-labs/otphp to latest
2026-02-06 20:14:26 +05:30
Chirag Aggarwal ab35806372 update system lib 2026-02-06 15:43:59 +05:30
Luke B. SilverandGitHub 9612ed338f Merge pull request #11262 from appwrite/chore-remove-auth-skip-from-workers
chore: remove auth skip from workers
2026-02-06 10:13:11 +00:00
Chirag Aggarwal 1219391c76 update composer 2026-02-06 15:27:42 +05:30
HemachandarandGitHub da7f5b7336 Move VCS repository APIs to Modules (#11259)
* Move VCS repository APIs to Modules

* add to http

* lint

* strategy fix

* rename to detections
2026-02-06 15:22:44 +05:30
loks0n ec418e60b7 chore: remove auth skip from workers 2026-02-06 09:44:10 +00:00
Chirag Aggarwal 455004a536 chore: upgrade spomky-labs/otphp to latest 2026-02-06 15:12:37 +05:30
Matej BačoandGitHub 2ad0135890 Merge pull request #11249 from appwrite/ser-1092-v2
Create rule in SSL task
2026-02-06 09:34:26 +01:00
Jake BarnbyandGitHub 6414c0ebda Merge pull request #11201 from appwrite/get-cursor-queries
Get cursor queries
2026-02-06 07:15:35 +00:00
Jake BarnbyandGitHub 801befeacd Merge branch '1.8.x' into get-cursor-queries 2026-02-06 05:59:48 +00:00
Jake BarnbyandGitHub 941d40e704 Merge pull request #11260 from appwrite/localhost-endpoint
Localhost endpoint CSV
2026-02-06 05:49:55 +00:00
fogelito 093bc7bf3d Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-06 07:14:07 +02:00
fogelito 90f10ce685 Use https 2026-02-06 07:13:39 +02:00
Hemachandar 45e74d7b3d rule 2026-02-06 00:14:15 +05:30
Chirag AggarwalandGitHub 4090ce853e Merge pull request #11256 from appwrite/release-cli-13.3.0
release cli sdk 13.3.0
2026-02-05 17:52:36 +05:30
Chirag Aggarwal af5c0da1d9 release cli sdk 13.3.0 2026-02-05 17:05:22 +05:30
Jake BarnbyandGitHub b5bb4c2e07 Merge pull request #11254 from appwrite/feat-collection-size
Expose row bytes used/bytes free on table/collection
2026-02-05 11:25:02 +00:00
Shmuel FogelandGitHub c492cfe9ab Merge pull request #11229 from appwrite/localhost-endpoint
Localhost endpoint
2026-02-05 13:00:00 +02:00
Jake Barnby a866f0a69f Gen specs 2026-02-05 23:50:16 +13:00
Jake Barnby 3b2c05eb8d Update desc 2026-02-05 23:50:10 +13:00
fogelito e28ba64dbf Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-05 12:36:25 +02:00
Jake BarnbyandGitHub 747b400616 Merge pull request #11251 from appwrite/fix-realtime-performance
Realtime query performance improvements
2026-02-05 10:15:58 +00:00
fogelito cb58da7279 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-05 12:13:02 +02:00
Matej BačoandGitHub 106458a79d Merge pull request #11208 from appwrite/ser-539-modules
Move project create & update APIs to Modules
2026-02-05 11:11:09 +01:00
fogelito d40b1707ba Remove var_dump 2026-02-05 12:09:54 +02:00
fogelito 8600d0e64f Remove var_dump 2026-02-05 12:04:57 +02:00
Jake BarnbyandGitHub 2bc8d80273 Merge branch '1.8.x' into feat-collection-size 2026-02-05 09:55:23 +00:00
Jake BarnbyandGitHub 1d94e60a37 Merge branch '1.8.x' into fix-realtime-performance 2026-02-05 09:54:42 +00:00
Jake Barnby 4f7337759f Expose row bytes used/bytes free on table/collection 2026-02-05 22:53:04 +13:00
Hemachandar 657f16031b lint 2026-02-05 15:11:17 +05:30
Jake BarnbyandGitHub fed8354c52 Merge pull request #11237 from appwrite/realtime-query-subscriptions
Realtime query subscriptions
2026-02-05 09:36:54 +00:00
Hemachandar cbfef909ee reset permissions 2026-02-05 15:03:27 +05:30
Hemachandar 107fadfe37 Merge branch '1.8.x' into ser-539-modules 2026-02-05 15:00:52 +05:30
Jake BarnbyandClaude Opus 4.5 e21614088f fix: Address CodeRabbit review comments
- Fix OR precheck bug: skip attribute existence check when OR conditions
  exist, since OR can match with partial attributes present
- Remove dead code in app/realtime.php (unused $names and $channels variables)
- Add tests for OR queries with missing attributes in one branch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 22:24:26 +13:00
HemachandarandGitHub 1c6880a78e Improve certificate sync logs (#11243)
* Add friendly message for async certificate generation

* better msg

* simplify

* remove escaping

* set certificateId
2026-02-05 14:48:37 +05:30
Jake BarnbyandClaude Opus 4.5 18125156d2 fix: Update compileAndFilter return type to nullable array
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 22:14:41 +13:00
Jake BarnbyandClaude Opus 4.5 765d334674 fix: Return null instead of empty array for non-matching queries
RuntimeQuery::filter() now returns null when the query doesn't match,
instead of an empty array. This distinguishes between "no match" and
"match with empty payload", fixing the issue where subscriptions with
empty payloads weren't being delivered.

Updated Realtime::getSubscribers() to check for null instead of using
!empty(), and updated all tests to expect null for non-matches.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 22:08:43 +13:00
Matej BačoandGitHub a46fa50c51 Merge pull request #11252 from appwrite/ser-1103-installations
Move VCS installation APIs to Modules
2026-02-05 10:07:35 +01:00
Matej BačoandGitHub cb81939604 Merge pull request #11253 from appwrite/ser-1033
Cleanup functions & sites attribute when installation is deleted
2026-02-05 10:04:38 +01:00
Jake BarnbyandClaude Opus 4.5 3e31094e1e fix: Fix unit test failures for RuntimeQuery and Messaging tests
- Add missing isSelectAll() method to RuntimeQuery class
- Update RuntimeQueryTest to use compile() before filter() since filter()
  expects pre-compiled query arrays, not Query objects
- Remove incorrect break statement in Realtime::getSubscribers() that was
  stopping iteration after the first matching channel, causing subscribers
  to not be found for subsequent channels
- Use local variable $subscriptionsByChannel to avoid unused variable warning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 21:57:00 +13:00
fogelito 074470c2c2 check _APP_MIGRATION_HOST 2026-02-05 10:35:35 +02:00
ArnabChatterjee20k f8f2f9ca25 removed reduntant test 2026-02-05 14:03:26 +05:30
fogelito aff180c517 docker-compose.yml 2026-02-05 10:30:21 +02:00
fogelito 5d1b63fd35 docker-compose.yml 2026-02-05 10:29:45 +02:00
Hemachandar aa30db2136 refactor 2026-02-05 13:51:32 +05:30
fogelito 64f8b9195b _APP_MIGRATION_HOST 2026-02-05 10:12:55 +02:00
fogelito 12b7f94c1f _APP_MIGRATION_HOST 2026-02-05 10:06:11 +02:00
Hemachandar 4aaa3bfd9f Cleanup functions & sites attribute when installation is deleted 2026-02-05 13:27:06 +05:30
HemachandarandGitHub 6dd928b73c Merge branch '1.8.x' into ser-1103-installations 2026-02-05 13:23:19 +05:30
Jake Barnby e490f375ee Batch groups 2026-02-05 20:48:40 +13:00
ArnabChatterjee20kandGitHub 96ea3391d3 Merge branch '1.8.x' into realtime-query-subscriptions 2026-02-05 13:01:28 +05:30
Hemachandar 7f824785d5 lint 2026-02-05 12:55:34 +05:30
Hemachandar 8c191fca92 wire module 2026-02-05 12:44:19 +05:30
fogelito 85ea4c8904 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-05 08:54:50 +02:00
fogelito 7e385fe87f localhosts 2026-02-05 08:52:12 +02:00
Jake Barnby d06759e33e Cleanup 2026-02-05 18:47:54 +13:00
Jake Barnby ecf0a1ec67 Avoid closures on hot path 2026-02-05 18:47:49 +13:00
Jake Barnby ead2afb9d4 Early exit on select all 2026-02-05 18:47:27 +13:00
Hemachandar 26b0447657 add to http 2026-02-04 20:41:46 +05:30
Matej BačoandGitHub f7f6b5187c Merge pull request #11248 from appwrite/feat-trusted-console-projects
Feat: Support trusted console projects
2026-02-04 16:10:36 +01:00
Hemachandar c3fbb83ed6 Move VCS installation APIs to Modules 2026-02-04 20:03:52 +05:30
Matej Bačo a860975b0e update docs 2026-02-04 15:23:10 +01:00
Matej Bačo ce99d80c2f ai review fixes 2026-02-04 15:09:02 +01:00
Hemachandar a30f12cd66 tiny 2026-02-04 19:31:00 +05:30
Hemachandar 0003443c77 Create rule in SSL task 2026-02-04 19:23:27 +05:30
Matej Bačo 227d61e194 Support trusted console projects 2026-02-04 14:45:38 +01:00
fogelito 4cf6f4e066 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-04 13:58:22 +02:00
Chirag AggarwalandGitHub b1409e5e8e Merge pull request #11246 from appwrite/fix/phpunit-bootstrap-global-register 2026-02-04 16:11:24 +05:30
Chirag Aggarwal a744cb0843 fix: add global scope declaration for $register in registers.php
PHPUnit's FileLoader::load() includes bootstrap files inside a function
scope. Without the global declaration, $register is created in local
scope and isn't accessible when other files (like resources.php) use
global $register to reference it.
2026-02-04 16:05:13 +05:30
fogelito 186ed5b61f fix graphQL 2026-02-04 12:29:20 +02:00
fogelito 522c9d3272 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-04 12:16:24 +02:00
Chirag AggarwalandGitHub 720ceabbbc Merge pull request #11244 from appwrite/simplify-messaging-span-country-codes 2026-02-04 15:45:14 +05:30
fogelito 5ff31e92d7 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint
# Conflicts:
#	composer.lock
2026-02-04 12:03:28 +02:00
Chirag Aggarwal 4ef43aacd9 Simplify country code span logging for internal SMS messages
Internal SMS always has a single recipient, so remove the unnecessary
count-based tracking and simplify to just log the country code directly.
2026-02-04 15:28:27 +05:30
Chirag AggarwalandGitHub ce49b8ecb8 Merge pull request #11239 from appwrite/upgrade-utopia-queue 2026-02-04 15:25:56 +05:30
fogelito 1db42c6fc7 revert lock 2026-02-04 11:43:56 +02:00
Chirag Aggarwal 9bd2966222 fix: versioning 2026-02-04 14:53:56 +05:30
Chirag Aggarwal 579f07f8bb fix unit test 2026-02-04 14:30:24 +05:30
fogelito febb7dc10a Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-04 10:53:26 +02:00
Chirag Aggarwal 4b02bf1aa8 use stable 2026-02-04 14:21:52 +05:30
Jake BarnbyandGitHub d45893d123 Merge pull request #11241 from appwrite/fix-leaks
Fix leaks
2026-02-04 08:31:27 +00:00
fogelito ffd568aecd Update 1.8.x 2026-02-04 10:05:54 +02:00
fogelito d811d1c534 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint
# Conflicts:
#	composer.lock
2026-02-04 10:04:52 +02:00
fogelito c2b87cff66 $this->webEndpoint, 2026-02-04 09:53:37 +02:00
fogelito ca03176ad6 $this->webEndpoint, 2026-02-04 09:53:19 +02:00
Chirag Aggarwal 946a1c8f34 try fix 2026-02-04 12:49:44 +05:30
Chirag Aggarwal 91c1572318 log 2026-02-04 12:23:55 +05:30
Chirag AggarwalandGitHub 05a3417297 Merge branch '1.8.x' into upgrade-utopia-queue 2026-02-04 12:23:18 +05:30
Jake BarnbyandGitHub 5e495aa2da Merge pull request #11240 from appwrite/fix-sites-domains
CLI update
2026-02-04 06:28:14 +00:00
Jake Barnby cdd2be175f Pre-parse queries 2026-02-04 19:27:57 +13:00
Jake Barnby 2f5e5d384f Array push on hot loop 2026-02-04 19:27:18 +13:00
Jake Barnby 2c790ecd84 Fix potential depth leak 2026-02-04 19:22:00 +13:00
Jake Barnby 5733972218 Fix webhook leak 2026-02-04 19:20:42 +13:00
Jake Barnby 2416eef4a7 Fix stats leak 2026-02-04 19:20:19 +13:00
Chirag AggarwalandGitHub 49195e5d6f Merge branch '1.8.x' into upgrade-utopia-queue 2026-02-04 11:22:23 +05:30
Chirag Aggarwal 1eaf5d5ffa replace app usage with http 2026-02-04 11:22:04 +05:30
Jake BarnbyandGitHub 17fc91aa19 Merge pull request #11238 from appwrite/fix-span-country-codes-logql
fix: use individual span attributes for country codes
2026-02-04 05:16:15 +00:00
Chirag Aggarwal 94d733ee63 use stable branch 2026-02-04 10:44:18 +05:30
Jake Barnby 17be0eb634 CLI sites domain fix 2026-02-04 18:04:48 +13:00
Chirag Aggarwal a419501ea7 chore: upgrade utopia queue 2026-02-04 10:28:16 +05:30
Chirag Aggarwal b89f474cb5 fix skill 2026-02-04 10:20:31 +05:30
Chirag Aggarwal 345d8f2db5 fix: use individual span attributes for country codes
Replace stringified JSON countryCodes attribute with individual
countryCode_{code} attributes to improve LogQL query compatibility.
2026-02-04 10:15:36 +05:30
fogelito 9bafa90ac8 _APP_HOSTNAMES 2026-02-03 17:39:29 +02:00
fogelito 736b72164e ci cd 2026-02-03 16:14:00 +02:00
ArnabChatterjee20k f82662e84e added dot escaping fix for channel names 2026-02-03 18:33:35 +05:30
Chirag AggarwalandGitHub 5537fe210e Merge pull request #11236 from appwrite/safeguard-span-finish 2026-02-03 18:32:32 +05:30
Chirag Aggarwal 4c08cb36a8 chore: safeguard span finish 2026-02-03 18:30:51 +05:30
Chirag AggarwalandGitHub 1ee3f993d0 Merge pull request #11234 from appwrite/update-cli-changelog 2026-02-03 17:36:43 +05:30
Jake BarnbyandGitHub 952f209a44 Merge pull request #11202 from appwrite/realtime-query-subscriptions 2026-02-03 11:44:13 +00:00
fogelito 7d193d9c36 force localhost 2026-02-03 13:41:02 +02:00
Jake BarnbyandGitHub c04353bd38 Merge pull request #11230 from appwrite/fix-responses 2026-02-03 11:37:09 +00:00
Chirag Aggarwal 69cfde440a chore: fix cli release + add changelog 2026-02-03 17:00:22 +05:30
Chirag AggarwalandGitHub 739b36b0c9 Merge pull request #11233 from appwrite/wrap-markdown-docs 2026-02-03 16:17:18 +05:30
fogelito 8b4f4d2fea localhost 2026-02-03 12:43:37 +02:00
Jake Barnby d2eb3f6684 Update database 2026-02-03 23:34:13 +13:00
Chirag Aggarwal 31b5a1cb36 chore: wrap all markdown docs with language fences 2026-02-03 15:48:48 +05:30
Chirag AggarwalandGitHub 727c29ec0c Merge pull request #11232 from appwrite/chore-add-pr-workflow-skill 2026-02-03 15:29:20 +05:30
fogelito 3da3b6a324 pull changes 2026-02-03 11:49:53 +02:00
fogelito 20e209038d Merge branch '1.8.x' of https://github.com/appwrite/appwrite into localhost-endpoint 2026-02-03 11:49:17 +02:00
fogelito c0cbf991ab Run tests 2026-02-03 11:48:48 +02:00
fogelito 91f3bf0244 Run tests 2026-02-03 11:47:21 +02:00
Chirag Aggarwal fab0c7ce7c Add PR workflow skill for AI agents
Documents that PRs should target version branches (e.g., 1.8.x)
instead of main, helping AI agents follow the correct workflow.
2026-02-03 15:12:47 +05:30
Jake Barnby 413a91bd0a Lint 2026-02-03 22:33:51 +13:00
Jake Barnby 23c3496a63 Add missing model map 2026-02-03 22:30:02 +13:00
ArnabChatterjee20k 0528c0d693 updated the case for select all and empty payload 2026-02-03 14:29:34 +05:30
ArnabChatterjee20k 4319612358 updated unit tests 2026-02-03 14:16:35 +05:30
Chirag AggarwalandGitHub b7df5cfcc1 Merge pull request #11228 from appwrite/feat-span-messaging-worker 2026-02-03 14:15:07 +05:30
ArnabChatterjee20k a10fbc64c7 updated subscription after permission change 2026-02-03 13:46:05 +05:30
fogelito f1a2e869a2 localhost setUp 2026-02-03 10:11:03 +02:00
Jake BarnbyandGitHub def3ce056f Merge pull request #11220 from appwrite/chore-upgrade-cache 2026-02-03 07:19:12 +00:00
Chirag Aggarwal ae720c3462 Fix country code extraction using locale config
Use the existing locale-phones config to accurately extract country
codes from phone numbers. Sorts codes by length descending to match
longer codes first (e.g., 1868 Trinidad before 1 USA).
2026-02-03 12:36:46 +05:30
Chirag Aggarwal e14a026415 Add country code extraction for external SMS messages
Extract country codes from target identifiers for external SMS
messages to match the behavior of internal SMS messages.
2026-02-03 12:32:21 +05:30
Chirag Aggarwal 52cb8a9c56 Improve span data with detailed metrics
Add richer span data for better observability:
- External messages: messageId, topic/user/target counts, recipient total,
  provider details, delivery stats (delivered/errors), final status
- Internal SMS: recipient count, country codes extracted from phone numbers,
  sender number, delivery status and count
2026-02-03 12:27:27 +05:30
Chirag Aggarwal 787e7aed43 Add Span tracing to Messaging worker
Replace Console logging with Span tracing in the Messaging worker
for better observability. Initialize Span storage and exporter in
worker.php to enable tracing in all workers.
2026-02-03 11:59:49 +05:30
ArnabChatterjee20k d18d4d8263 updated subscription process 2026-02-03 11:43:23 +05:30
premtsd-codeandGitHub b4e98bd950 Merge branch '1.8.x' into chore-upgrade-cache 2026-02-03 05:58:33 +00:00
Chirag AggarwalandGitHub 92d2310a53 Merge pull request #11227 from appwrite/fix-deprecation-warnings 2026-02-03 11:19:23 +05:30
ArnabChatterjee20kandGitHub 29b6adf407 Merge branch '1.8.x' into realtime-query-subscriptions 2026-02-03 10:46:34 +05:30
ArnabChatterjee20k 10e971aded updated tests and empty subscriptionsid issue 2026-02-03 10:46:01 +05:30
premtsd-codeandGitHub ca464f8a71 Merge branch '1.8.x' into chore-upgrade-cache 2026-02-03 04:44:12 +00:00
Chirag Aggarwal 6c4394947f Fix Safe PHP deprecation warnings 2026-02-03 10:11:12 +05:30
Chirag AggarwalandGitHub 2a15c91768 Merge pull request #11226 from appwrite/feat-functions-queue-job-ttl 2026-02-03 10:09:25 +05:30
Chirag Aggarwal f15e9eb356 Add 7-day job TTL for functions queue
Set jobTTL to 7 days for FUNCTIONS_QUEUE_NAME using a match expression
for easy extensibility to other queues in the future.
2026-02-03 09:24:32 +05:30
Prem Palanisamy cbe7ce7269 fix merge conflict in composer.lock 2026-02-02 21:48:20 +00:00
premtsd-codeandGitHub f2cfa4465f Merge branch '1.8.x' into chore-upgrade-cache 2026-02-02 21:03:45 +00:00
HemachandarandGitHub f648d82c8c Remove cleanup-stale-executions from Interval task (#11224) 2026-02-03 01:18:34 +05:30
Prem Palanisamy 8117ab9542 simplify CacheTest to match other Health tests 2026-02-02 19:07:50 +00:00
Prem Palanisamy 255098ed9c use env vars for cache reconnection tests 2026-02-02 18:36:16 +00:00
Prem Palanisamy 914be48e25 fix: add null check for cache adapter 2026-02-02 18:25:41 +00:00
premtsd-codeandGitHub 47e406baa0 Merge branch '1.8.x' into chore-upgrade-cache 2026-02-02 18:22:02 +00:00
ArnabChatterjee20k 3c2f0cc982 updated to new slot based filtering for realtime 2026-02-02 21:50:23 +05:30
Matej BačoandGitHub a967ee9b1b Merge pull request #11213 from appwrite/feat-array-domains-env
Feat: Array domain variables
2026-02-02 17:06:55 +01:00
ArnabChatterjee20k 6e576b5140 added realtime query new channel and channel wise filters 2026-02-02 19:45:21 +05:30
premtsd-codeandGitHub ab689af94b Merge branch '1.8.x' into chore-upgrade-cache 2026-02-02 13:21:22 +00:00
Prem Palanisamy e7b4f03227 improve cache reconnection tests 2026-02-02 13:19:41 +00:00
HemachandarandGitHub 630d3376a6 Use Swoole timers in Interval task (#11205)
* Use Swoole timers in `Interval` task

* refactor

* block coroutine from exiting

* nit

* Add span

* cleanup unused var

* camelCase
2026-02-02 18:47:54 +05:30
Jake BarnbyandGitHub a25977c6b8 Merge pull request #11222 from appwrite/release-array-param-enums 2026-02-02 13:08:51 +00:00
Chirag Aggarwal 19bfda541f Merge branch '1.8.x' into release-array-param-enums 2026-02-02 18:35:20 +05:30
Chirag Aggarwal 2ac265baea update composer 2026-02-02 18:34:24 +05:30
Prem Palanisamy a52f7f2485 fix: ensure Redis restart in cache reconnection tests 2026-02-02 12:57:40 +00:00
Prem Palanisamy 6876828750 add cache reconnection support 2026-02-02 12:49:58 +00:00
Chirag Aggarwal 02c92d95b7 examples and changelog 2026-02-02 18:04:02 +05:30
Chirag Aggarwal eb97a0e9b2 changelog changes 2026-02-02 17:57:50 +05:30
Chirag Aggarwal ae6a81bd12 update specs 2026-02-02 17:54:57 +05:30
premtsd-codeandGitHub 6f7da6d622 Merge branch '1.8.x' into chore-upgrade-cache 2026-02-02 12:23:42 +00:00
Prem Palanisamy e79dc39d5d chore: update composer.lock 2026-02-02 12:21:08 +00:00
Chirag Aggarwal 4d062b818d update examples 2026-02-02 17:26:20 +05:30
Prem Palanisamy a1dad882e4 chore: update composer.lock 2026-02-02 11:02:42 +00:00
Prem Palanisamy abb546d099 chore: replace dev dependencies with stable versions 2026-02-02 11:00:20 +00:00
Jake BarnbyandGitHub dfdbcf015c Merge pull request #11207 from appwrite/migration-endpoint 2026-02-02 10:34:04 +00:00
Chirag AggarwalandGitHub 29ab9ef9d2 Merge pull request #11223 from appwrite/update-message-logging 2026-02-02 15:46:43 +05:30
Chirag Aggarwal 52174816b2 chore: update messaging worker logging 2026-02-02 15:39:12 +05:30
Chirag Aggarwal 9ee318de3c feat: release array param enums support to all sdks 2026-02-02 15:34:32 +05:30
fogelito 2e5b232e2b line 2026-02-02 11:34:57 +02:00
fogelito 53b8886729 Remove env variable 2026-02-02 11:29:38 +02:00
fogelito e9e51d5334 lock 2026-02-02 11:27:50 +02:00
fogelito c1161ff27c check isset 2026-02-02 08:13:24 +02:00
Prem Palanisamy 1854d01e6e chore: upgrade utopia-php dependencies for cache reconnection support 2026-02-02 06:13:00 +00:00
fogelito f530ec3db5 Remove dbg 2026-02-01 15:57:30 +02:00
fogelito 3502cad4a5 formatting 2026-02-01 15:53:41 +02:00
fogelito 05e1bb242f stopOnFailure false 2026-02-01 15:52:29 +02:00
fogelito e760a1386b Use credentials 2026-02-01 15:51:41 +02:00
fogelito 644dcb8fb1 empty $credentials 2026-02-01 15:11:29 +02:00
fogelito 40dede6503 Use apiHostname 2026-02-01 14:54:42 +02:00
fogelito d19eec9849 var dump 2026-02-01 12:31:17 +02:00
fogelito 88044eaa71 check key 2026-02-01 12:15:32 +02:00
fogelito 6011a7c66d http 2026-02-01 12:00:04 +02:00
fogelito 771dd92a14 https 2026-02-01 11:55:01 +02:00
fogelito e1f8d0bbbc remove extra vars 2026-02-01 10:38:04 +02:00
fogelito 7da2bdf4da remove extra vars 2026-02-01 10:36:38 +02:00
fogelito d89539b56a Merge branch '1.8.x' of https://github.com/appwrite/appwrite into migration-endpoint 2026-02-01 08:49:01 +02:00
fogelito 23f08ef144 formatting 2026-02-01 08:46:39 +02:00
Matej Bačo ce1bb3fd29 ai review fixes 2026-01-30 22:23:36 +01:00
Matej Bačo fea4994ef0 ai review fixes 2026-01-30 21:30:00 +01:00
Matej Bačo 4807dcf529 Array support for all functions domain usecases 2026-01-30 16:48:40 +01:00
Matej Bačo 817475a251 Add tests 2026-01-30 16:24:41 +01:00
Matej Bačo 150c9033cc Support arrays in domains env variables 2026-01-30 16:20:46 +01:00
Matej BačoandGitHub 8938dcf8f4 Merge pull request #11212 from appwrite/chore-preview-domain-under-platform
Chore: Platform to defien preview domain
2026-01-30 14:29:17 +01:00
Matej Bačo edbec1ef41 AI review fixes 2026-01-30 13:29:28 +01:00
Matej Bačo edceb3e0db move preview domain under platform for future customizability 2026-01-30 13:23:37 +01:00
Jake BarnbyandGitHub d3974776be Merge pull request #11210 from appwrite/fix-events 2026-01-30 10:05:18 +00:00
HemachandarandGitHub 1110fdb719 Add appendVariables method to Mail event (#11166)
* Add `addVariable` method to Mail event

* append variables

* Update template
2026-01-30 15:20:53 +05:30
Jake Barnby 252dc6b993 Fix test 2026-01-30 22:44:19 +13:00
Hemachandar a987195f6f more lint 2026-01-30 14:47:55 +05:30
Hemachandar 2144f27059 lint + feedback 2026-01-30 14:36:38 +05:30
Jake Barnby 67e43cc1a5 Push vs merge 2026-01-30 21:48:36 +13:00
Jake Barnby d4b0ea64ad Fix event caching 2026-01-30 21:41:01 +13:00
Hemachandar cb0f2299fb Upgrade DB 2026-01-30 13:58:18 +05:30
fogelito a38b6cc78d lock 2026-01-30 10:05:11 +02:00
fogelito 9d7bc04089 throw 2026-01-30 10:04:09 +02:00
fogelito 5c61f7232a _APP_MIGRATION_ENDPOINT 2026-01-29 18:22:56 +02:00
Hemachandar 01cc9bcd1f Move project create & update APIs to Modules 2026-01-29 21:35:42 +05:30
fogelito 6e6081f693 set credentials 2026-01-29 17:33:37 +02:00
fogelito ddb72c7546 set credentials 2026-01-29 17:32:28 +02:00
fogelito 1b82d32436 Merge branch '1.8.x' of https://github.com/appwrite/appwrite into migration-endpoint
# Conflicts:
#	.env
2026-01-29 17:15:05 +02:00
fogelito e8d40a48dc endpoint 2026-01-29 17:13:01 +02:00
Jake BarnbyandGitHub 6ccc8b9261 Merge pull request #11174 from appwrite/feat-string-types 2026-01-29 14:39:06 +00:00
Jake Barnby 0d79ab0fd3 Update specs 2026-01-30 02:21:32 +13:00
Jake Barnby c9aac86a6b Merge remote-tracking branch 'origin/1.8.x' into feat-string-types
# Conflicts:
#	composer.lock
2026-01-30 02:07:33 +13:00
Matej BačoandGitHub 8c626ea2ca Merge pull request #11206 from appwrite/chore-upgrade-templates
Chore: Upgrade templates version
2026-01-29 13:53:32 +01:00
Jake BarnbyandGitHub 9d953f898d Merge pull request #11007 from appwrite/new-pool-adapter 2026-01-29 12:44:13 +00:00
Matej BačoandGitHub c466ffcf7e Merge branch '1.8.x' into chore-upgrade-templates 2026-01-29 12:54:58 +01:00
Matej BačoandGitHub 366e578cf8 Merge pull request #11189 from appwrite/chore-usecases-enums
Chore: Proper quality to template usecases enums
2026-01-29 12:49:38 +01:00
Matej Bačo 63ceea7aca Fix wrong ref versions 2026-01-29 12:30:47 +01:00
Matej BačoandGitHub 1df09c652d Merge branch '1.8.x' into chore-usecases-enums 2026-01-29 12:12:17 +01:00
ArnabChatterjee20k 6b9f48aa76 updated lock 2026-01-29 16:32:49 +05:30
ArnabChatterjee20k c8c5fbaca2 updated lock 2026-01-29 16:18:23 +05:30
Matej Bačo 1131e4ed18 Revert "Upgrade template versions"
This reverts commit a2d920195d.
2026-01-29 11:46:05 +01:00
Matej Bačo a2d920195d Upgrade template versions 2026-01-29 11:39:42 +01:00
ArnabChatterjee20k 5753c7694d updated lock 2026-01-29 14:52:16 +05:30
ArnabChatterjee20k 0d057390ab updated composer and docker compose 2026-01-29 14:34:20 +05:30
ArnabChatterjee20k e82345be40 updated tests 2026-01-29 14:22:08 +05:30
ArnabChatterjee20k 077068e9fc updated tests 2026-01-29 14:20:05 +05:30
ArnabChatterjee20k 64392c1520 fixed failing tests 2026-01-29 11:38:20 +05:30
ArnabChatterjee20kandGitHub 7973824321 Merge branch '1.8.x' into new-pool-adapter 2026-01-29 11:20:02 +05:30
Jake BarnbyandGitHub b1ca73a041 Merge pull request #11193 from premtsd-code/fix-10612-validate-relationship-document-id 2026-01-29 04:56:47 +00:00
premtsd-codeandGitHub 845aa5c363 Merge branch '1.8.x' into fix-10612-validate-relationship-document-id 2026-01-28 18:45:53 +00:00
Prem Palanisamy 23dae85d2f Sync composer.lock with 1.8.x 2026-01-28 18:45:11 +00:00
Luke B. SilverandGitHub 7bf02811f2 Merge pull request #11198 from appwrite/refactor-remove-magic-class-strings
refactor: remove magic class strings
2026-01-28 16:56:31 +00:00
premtsd-codeandGitHub 23c1f8fd5a Merge branch '1.8.x' into fix-10612-validate-relationship-document-id 2026-01-28 15:21:40 +00:00
Matej BačoandGitHub b9cb81496d Merge pull request #11203 from appwrite/chore-deprecate-project-attributes
Chore: Remove unnessessary attributes
2026-01-28 15:58:58 +01:00
Matej Bačo e22e8d6a5f Upgrade phpunit for vuln 2026-01-28 14:55:13 +01:00
ArnabChatterjee20kandGitHub 24385355a0 Merge branch '1.8.x' into new-pool-adapter 2026-01-28 19:24:59 +05:30
ArnabChatterjee20k 44b52b5a00 updated swoole and pools 2026-01-28 19:24:33 +05:30
ArnabChatterjee20k 6c29dd055c updated php unit 2026-01-28 19:21:01 +05:30
Matej Bačo 14a96a2b56 Remove unnessessary attributes 2026-01-28 14:50:17 +01:00
Matej BačoandGitHub 273364114a Merge pull request #10988 from appwrite/chore-php-types
Feat: CE support for Platform API
2026-01-28 14:45:42 +01:00
ArnabChatterjee20kandGitHub 72db0c721b Merge branch '1.8.x' into realtime-query-subscriptions 2026-01-28 19:06:30 +05:30
ArnabChatterjee20k cbe13d71f1 removed redundant key 2026-01-28 19:04:42 +05:30
fogelito 95ce53b66e Exception 2026-01-28 15:15:33 +02:00
ArnabChatterjee20k b785dea7ac added query per subscription and queryKeys along with the messages 2026-01-28 18:40:30 +05:30
fogelito f35aad0816 Remove extra line 2026-01-28 14:55:09 +02:00
fogelito 69e6c0afc0 getCursorQueries 2026-01-28 14:53:24 +02:00
Prem Palanisamy 2f3fa9e0d3 sync CONTRIBUTING.md with 1.8.x 2026-01-28 11:42:51 +00:00
premtsd-codeandGitHub 71670f8169 Merge branch '1.8.x' into fix-10612-validate-relationship-document-id 2026-01-28 11:17:50 +00:00
Matej BačoandGitHub a4a589c535 Merge branch '1.8.x' into chore-php-types 2026-01-28 11:44:51 +01:00
Prem Palanisamy cbe2d2383d chore: update phpunit to 9.6.34 (security fix) 2026-01-28 10:24:11 +00:00
Prem Palanisamy aef7b8df38 fix: use RELATIONSHIP_VALUE_INVALID exception for validation errors 2026-01-28 08:41:10 +00:00
ArnabChatterjee20k 125394166a updated docker appwrite image base 2026-01-28 12:04:26 +05:30
ArnabChatterjee20k 68cb03d22c updated to swoole 6 2026-01-27 19:16:57 +05:30
Prem Palanisamy 7f3ea98924 refactor: use array_is_list() and assertEventually helper 2026-01-27 13:00:29 +00:00
ArnabChatterjee20k 87db62b018 updated env 2026-01-27 16:39:08 +05:30
ArnabChatterjee20k 5fe5445283 Merge remote-tracking branch 'origin/1.8.x' into new-pool-adapter 2026-01-27 16:37:58 +05:30
ArnabChatterjee20k 303fca0fd5 updated pools 2026-01-27 16:36:28 +05:30
Prem Palanisamy d182c85330 fix: reject unsupported relationship value types 2026-01-27 09:35:45 +00:00
Prem Palanisamy d792d3bbea refactor: use getId() instead of getAttribute('$id') 2026-01-27 09:25:39 +00:00
Prem Palanisamy 00d091513d refactor: simplify relationship validation code 2026-01-27 06:59:53 +00:00
Prem Palanisamy 63e6a51af1 test: add assertion for relationship column polling 2026-01-26 15:45:42 +00:00
Prem Palanisamy 1ee2539ce0 fix: generate unique ID before validation per coderabbit suggestion 2026-01-26 15:45:42 +00:00
premtsd-codeandGitHub cb32dc40ec Merge branch 'main' into fix-10612-validate-relationship-document-id 2026-01-26 14:06:44 +00:00
Prem Palanisamy f66e0c2ff5 refactor: separate validation from normalization in validateRelationship 2026-01-26 14:05:56 +00:00
Prem Palanisamy aed9816d1e fix: validate relationship document ID 2026-01-26 13:48:32 +00:00
Matej Bačo 752731050e Proper quality to template usecases enums 2026-01-25 21:55:52 +01:00
Jake Barnby 368113ade5 Merge branch 'feat-string-types' of github.com:appwrite/appwrite into feat-string-types
# Conflicts:
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Create.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Columns/String/Update.php
2026-01-24 16:24:35 +13:00
Jake Barnby f1d1c5a737 Merge remote-tracking branch 'origin/1.8.x' into feat-string-types
# Conflicts:
#	composer.lock
2026-01-24 16:23:39 +13:00
Jake Barnby 5815008994 Update deprecation 2026-01-24 16:23:05 +13:00
Jake BarnbyandGitHub ac83d417aa Merge pull request #11183 from appwrite/copilot/sub-pr-11174 2026-01-24 14:47:34 +13:00
copilot-swe-agent[bot]andabnegate 9993c15fb8 chore: revert String attribute routes deprecation to 1.8.0
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 01:15:15 +00:00
copilot-swe-agent[bot]andabnegate 11aecdf33f chore: mark TablesDB String column routes as deprecated
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 01:11:03 +00:00
copilot-swe-agent[bot]andabnegate a088e85a24 chore: update String attribute deprecation to 1.9.0
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 01:01:49 +00:00
copilot-swe-agent[bot]andabnegate 1fd687b6b7 chore: update composer.lock after reverting to 4.*
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 00:30:56 +00:00
copilot-swe-agent[bot]andabnegate aa26823549 chore: revert utopia-php/database to 4.*
Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-01-24 00:26:11 +00:00
copilot-swe-agent[bot] 109e16362d Initial plan 2026-01-24 00:23:12 +00:00
Jake BarnbyandGitHub 294518d014 Merge branch '1.8.x' into feat-string-types 2026-01-24 13:21:30 +13:00
Jake BarnbyandClaude Opus 4.5 046a7cd90e test: skip update tests pending utopia-php/database fix
The updateAttribute method in utopia-php/database does not yet support
VARCHAR, TEXT, MEDIUMTEXT, and LONGTEXT types. These tests are skipped
until the upstream library adds support for updating these attribute types.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 06:15:21 +13:00
Jake BarnbyandClaude Opus 4.5 fea210c9ed chore: use utopia-php/database dev-feat-string-types branch
The string types feature requires the dev-feat-string-types branch of
utopia-php/database which includes support for VARCHAR, TEXT, MEDIUMTEXT,
and LONGTEXT types in the updateAttribute method.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 05:51:28 +13:00
Jake BarnbyandClaude Opus 4.5 760f065711 fix: reduce longtext size to fit within INT column limit
The size field in the attributes collection is stored as a signed 32-bit
integer (VAR_INTEGER). The longtext size of 4294967295 (2^32-1) exceeds
the maximum value of 2147483647 (2^31-1), causing attribute creation to
fail with a 400 error (document_invalid_structure).

Changed the longtext size to 2147483647 which is the maximum value that
fits within the signed 32-bit integer constraint of the schema.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 05:24:26 +13:00
Jake BarnbyandClaude Opus 4.5 c42fbc49f5 fix: remove max-size varchar test that exceeds row width limit
The test tried to create multiple VARCHAR attributes in the same
collection, including a 16381-character varchar. The cumulative
row width exceeded MariaDB's 65535 byte row limit, causing the
test to fail with a 400 error.

Calculation: 1067 (base) + 1021 (255*4+1) + 401 (100*4+1) + 201
(50*4+1) + 20 (array) + 65526 (16381*4+2) = 68236 bytes > 65535

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 04:25:06 +13:00
Jake BarnbyandClaude Opus 4.5 e20851e7a8 fix: reorder imports to satisfy linter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 03:53:42 +13:00
Jake Barnby 1b7cc0b5f9 Add docs 2026-01-23 01:51:58 +13:00
Jake Barnby 60a0b663b9 Add tests 2026-01-23 01:51:52 +13:00
Jake Barnby e6496ec453 Add routes 2026-01-23 01:51:43 +13:00
Jake Barnby 07204d748d Add response models 2026-01-23 01:51:24 +13:00
Matej Bačo b317f85fb6 Fix depricated schema 2026-01-21 16:27:09 +01:00
Matej Bačo 8e98c08a23 Fix failing tests 2026-01-21 16:05:43 +01:00
Matej Bačo 6ebc36d0f3 Fix merge conflict 2026-01-21 14:47:18 +01:00
Matej Bačo e5ed7c835a Merge branch '1.8.x' into chore-php-types 2026-01-21 14:43:22 +01:00
ArnabChatterjee20k d095f25a94 updated composer 2026-01-19 11:04:24 +05:30
ArnabChatterjee20k 069e4c6001 Merge remote-tracking branch 'origin/1.8.x' into new-pool-adapter 2026-01-19 11:03:32 +05:30
ArnabChatterjee20k fc04e17a69 updated env 2026-01-16 17:37:56 +05:30
ArnabChatterjee20k a7b729b9a7 updated deps 2026-01-16 15:33:18 +05:30
ArnabChatterjee20k a19705a46f Merge remote-tracking branch 'upstream/1.8.x' into new-pool-adapter 2026-01-16 12:31:57 +05:30
Matej Bačo 5d5a14bd77 PR review fixes 2026-01-15 16:16:09 +01:00
Matej Bačo 1308706023 Merge branch '1.8.x' into chore-php-types 2026-01-15 15:52:51 +01:00
Matej Bačo cd0d609229 quality improv 2026-01-10 17:01:31 +01:00
Matej Bačo 497e5f8d00 tests fixes 2026-01-10 16:57:23 +01:00
Matej Bačo 60e1efb8cb Merge conflict fix 2026-01-10 16:42:45 +01:00
Matej Bačo 3d4b29e0f1 Merge branch '1.8.x' into chore-php-types 2026-01-10 16:42:38 +01:00
Matej Bačo dad21a912e PR review changes 2026-01-10 16:35:09 +01:00
Steven NguyenandGitHub f0191202a0 Merge pull request #10342 from tejas-raskar/doc-10339-fix-inconsistent-dir-structure-in-contribution-docs
docs: update the directory structure in CONTRIBUTING.md
2026-01-02 15:59:24 -08:00
ArnabChatterjee20kandGitHub f338a819e1 Merge branch '1.8.x' into new-pool-adapter 2025-12-30 13:23:01 +05:30
ArnabChatterjee20k 2e82f0c2ec fix: update coroutine pool handling and configuration 2025-12-30 13:03:59 +05:30
Matej Bačo 00b5236dea simplify diff 2025-12-29 18:41:35 +01:00
Matej Bačo df57b59786 Merge branch '1.8.x' into chore-php-types 2025-12-29 13:24:50 +01:00
Matej Bačo eda189dbf1 AI review improvements 2025-12-29 13:24:26 +01:00
Matej Bačo ee911e3df6 Finalize unit key tests 2025-12-29 11:21:49 +01:00
Matej Bačo eb2c616089 Improve key unit tests 2025-12-29 10:47:27 +01:00
Matej Bačo 5f5d9b4fcb Add async key cleanup 2025-12-29 09:04:47 +01:00
Matej Bačo 7c56a76feb self PR review fixes 2025-12-29 08:59:07 +01:00
ArnabChatterjee20kandGitHub 7026052e04 Merge branch '1.8.x' into new-pool-adapter 2025-12-29 11:58:36 +05:30
Matej Bačo 04f660e44b Dedicate project test 2025-12-28 10:06:29 +01:00
Matej Bačo b4c1b96d43 Fix General tests 2025-12-27 19:28:08 +01:00
Matej Bačo 44d8c66e95 Merge branch '1.8.x' into chore-php-types 2025-12-27 19:18:45 +01:00
Matej Bačo 6774de4eef add todo 2025-12-27 19:18:25 +01:00
Matej Bačo ee0f15eed6 QA bug fixing 2025-12-27 19:08:12 +01:00
Matej Bačo c54d1d29a5 Update stats of all key ypes 2025-12-27 18:44:01 +01:00
ArnabChatterjee20k 11f23d99fb Revert "temp-check: force purging cache"
This reverts commit 00e00ff166.
2025-12-24 17:09:01 +05:30
ArnabChatterjee20k 00e00ff166 temp-check: force purging cache 2025-12-24 16:55:13 +05:30
ArnabChatterjee20kandGitHub 57c80d5967 Merge branch '1.8.x' into new-pool-adapter 2025-12-24 16:48:25 +05:30
ArnabChatterjee20k f36dd2748c fixed tests 2025-12-24 16:22:02 +05:30
ArnabChatterjee20k 2baa33351d fix tests 2025-12-24 14:38:43 +05:30
ArnabChatterjee20k f7668e0411 * added concurrent traffic load tests for realtime
* added assert eventual case for project console
* linting
2025-12-24 13:41:31 +05:30
ArnabChatterjee20k 62e881b7e9 reverted the use retry mechanism of the pools 2025-12-23 20:34:59 +05:30
Matej Bačo c08acedf6a Fix key test 2025-12-23 15:12:41 +01:00
ArnabChatterjee20k c03cd258ce changed the pool size distribution 2025-12-23 18:30:53 +05:30
Matej Bačo 9477a5d980 Fix extensability of collections 2025-12-23 13:30:43 +01:00
ArnabChatterjee20k 7e4d404549 updated composer json 2025-12-23 17:38:52 +05:30
Matej Bačo c0c1d693c2 DB schema update for keys 2025-12-23 13:06:25 +01:00
Matej Bačo 6e47fb6c70 Implement auth for organization and account keys 2025-12-23 13:06:19 +01:00
ArnabChatterjee20k 8df22db3eb added swoole based pools for realtime 2025-12-23 17:33:34 +05:30
Matej Bačo cca49f8f6a Improve docs 2025-12-23 12:11:01 +01:00
Matej Bačo b0bd9e5b78 Fix security requirement 2025-12-23 12:09:07 +01:00
Matej Bačo 6f7cca7d68 Allow key header for account keys 2025-12-23 12:06:30 +01:00
Matej Bačo bce3ce85d5 account key support 2025-12-23 11:59:38 +01:00
Matej Bačo 09d71e73af keys list to be public as its reused for more types of keys 2025-12-22 11:37:38 +01:00
Matej Bačo 465912822f Mark reused key response public 2025-12-22 11:32:45 +01:00
Matej Bačo ca43281fa9 Simplify PR 2025-12-20 09:20:39 +01:00
Matej Bačo c69382f29f Fix invalid index 2025-12-19 16:11:43 +01:00
Matej Bačo d45d27b780 Merge branch '1.8.x' into chore-php-types 2025-12-19 16:06:46 +01:00
Matej BačoandGitHub 859d146e85 Apply suggestions from code review 2025-12-19 13:41:18 +01:00
Matej Bačo 69d5ce0f55 Switch over to resource-based key DB structure 2025-12-19 13:40:32 +01:00
Matej Bačo 0a3877a900 New DB schema 2025-12-19 13:09:34 +01:00
Matej Bačo 31b9ff4f05 Merge branch '1.8.x' into chore-php-types 2025-12-19 12:26:51 +01:00
Matej Bačo 1c3f778da9 PR review fix 2025-12-19 12:26:15 +01:00
Steven NguyenandGitHub 44ec8de6bf Merge pull request #10946 from appwrite/1.8.x
Sync 1.8.x into main
2025-12-18 17:06:43 -08:00
Matej Bačo 6f16b56f31 Allow Key extensions 2025-12-18 15:55:11 +01:00
Matej Bačo 59f178d634 Improve PHP types for extensability 2025-12-18 13:37:50 +01:00
Tejas Raskar 80ad740d36 docs: update the directory structure in CONTRIBUTING.md 2025-08-20 14:34:43 +05:30
7000 changed files with 53540 additions and 8290 deletions
+31
View File
@@ -0,0 +1,31 @@
# PR Workflow for Appwrite
## Branch Targeting
**Important:** Appwrite does NOT use `main` as the primary development branch.
PRs should target the **current version branch** (e.g., `1.8.x`, `1.9.x`), not `main`.
### How to determine the correct target branch
1. Check which version branch you're currently on: `git branch --show-current`
2. Look for branches matching the pattern `X.Y.x` (e.g., `1.8.x`, `1.9.x`)
3. The current active development branch is typically the highest version number with the `.x` suffix
### When creating PRs
Always use the version branch as the base:
```bash
# Correct - targets the version branch
gh pr create --base 1.8.x --title "Your PR title" --body "..."
# Wrong - do not target main
gh pr create --base main ...
```
### Branch naming convention
- `X.Y.x` branches (e.g., `1.8.x`) - Active development branches for each minor version
- `main` - Not used for regular PRs; reserved for release management
- Feature branches should be created from and merged back into the current version branch
+5 -2
View File
@@ -10,6 +10,7 @@ _APP_CONSOLE_SESSION_ALERTS=enabled
_APP_CONSOLE_WHITELIST_IPS=
_APP_CONSOLE_COUNTRIES_DENYLIST=AQ
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_MIGRATION_HOST=appwrite
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io
_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
@@ -25,8 +26,9 @@ _APP_OPENSSL_KEY_V1=your-secret-key
_APP_DNS=172.16.238.100 # CoreDNS
_APP_DOMAIN=appwrite.test
_APP_CONSOLE_DOMAIN=localhost
_APP_CONSOLE_TRUSTED_PROJECTS=trusted-project,another-trusted-project
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_SITES=sites.localhost,rebranded.localhost
_APP_DOMAIN_TARGET_CNAME=cname.localhost
_APP_DOMAIN_TARGET_A=203.0.0.1
_APP_DOMAIN_TARGET_AAAA=::1
@@ -102,7 +104,7 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
_APP_STATS_RESOURCES_INTERVAL=30
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_INTERVAL_DOMAIN_VERIFICATION=60
_APP_INTERVAL_DOMAIN_VERIFICATION=120
_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300
_APP_USAGE_STATS=enabled
_APP_LOGGING_CONFIG=
@@ -130,3 +132,4 @@ _APP_PROJECT_REGIONS=default
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for
_APP_POOL_ADAPTER=stack
+2 -1
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.10.6 AS base
FROM appwrite/base:0.11.5 AS base
LABEL maintainer="team@appwrite.io"
@@ -81,6 +81,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-databases && \
chmod +x /usr/local/bin/worker-deletes && \
chmod +x /usr/local/bin/worker-executions && \
chmod +x /usr/local/bin/worker-functions && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
+6
View File
@@ -30,6 +30,9 @@ use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\Span\Exporter;
use Utopia\Span\Span;
use Utopia\Span\Storage;
use Utopia\System\System;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
@@ -41,6 +44,7 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
// require controllers after overwriting runtimes
require_once __DIR__ . '/controllers/general.php';
global $register;
CLI::setResource('register', fn () => $register);
CLI::setResource('cache', function ($pools) {
@@ -325,4 +329,6 @@ $cli
$cli->shutdown()->action(fn () => Timer::clearAll());
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
Span::setStorage(new Storage\Coroutine());
Span::addExporter(new Exporter\Stdout());
run($cli->run(...));
+2 -2
View File
@@ -26,8 +26,8 @@ unset($common['files']);
$collections = [
'buckets' => $buckets,
'databases' => $databases,
'projects' => array_merge($projects, $common),
'console' => array_merge($platform, $common),
'projects' => array_merge_recursive($projects, $common),
'console' => array_merge_recursive($platform, $common),
'logs' => $logs,
];
+7 -36
View File
@@ -633,29 +633,7 @@ $platformCollections = [
'name' => 'keys',
'attributes' => [
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceType',
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -666,7 +644,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => 'resourceId',
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -677,7 +655,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => 'resourceInternalId',
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -756,21 +734,14 @@ $platformCollections = [
],
'indexes' => [
[
'$id' => ID::custom('_key_project'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_resource',
'$id' => ID::custom('_key_resource'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType', 'resourceInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
'lengths' => [],
'orders' => [],
],
[
'$id' => '_key_accessedAt',
'$id' => ID::custom('_key_accessedAt'),
'type' => Database::INDEX_KEY,
'attributes' => ['accessedAt'],
'lengths' => [],
+30
View File
@@ -357,6 +357,11 @@ return [
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::USER_JWT_AND_COOKIE_SET => [
'name' => Exception::USER_JWT_AND_COOKIE_SET,
'description' => 'JWT and cookie used in the same request. Use either `setJWT` or `setCookie`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::API_KEY_EXPIRED => [
'name' => Exception::API_KEY_EXPIRED,
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
@@ -1074,6 +1079,11 @@ return [
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ACCOUNT_KEY_EXPIRED => [
'name' => Exception::ACCOUNT_KEY_EXPIRED,
'description' => 'The account API key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ROUTER_HOST_NOT_FOUND => [
'name' => Exception::ROUTER_HOST_NOT_FOUND,
'description' => 'Host is not trusted. This could occur because you have not configured a custom domain. Add a custom domain to your project first and try again.',
@@ -1129,6 +1139,11 @@ return [
'description' => 'Key with the requested ID could not be found.',
'code' => 404,
],
Exception::KEY_ALREADY_EXISTS => [
'name' => Exception::KEY_ALREADY_EXISTS,
'description' => 'Key with the same ID already exists. Try again with a different ID.',
'code' => 409,
],
Exception::PLATFORM_NOT_FOUND => [
'name' => Exception::PLATFORM_NOT_FOUND,
'description' => 'Platform with the requested ID could not be found.',
@@ -1328,4 +1343,19 @@ return [
'description' => 'Target has an invalid provider type.',
'code' => 400,
],
Exception::USER_ID_MISSING => [
'name' => Exception::USER_ID_MISSING,
'description' => 'When using account API key, make sure to pass x-appwrite-user header with your user ID.',
'code' => 403,
],
Exception::ORGANIZATION_ID_MISSING => [
'name' => Exception::ORGANIZATION_ID_MISSING,
'description' => 'When using organization API key, make sure to pass x-appwrite-organization header with your organization ID.',
'code' => 403,
],
Exception::PROJECT_ID_MISSING => [
'name' => Exception::PROJECT_ID_MISSING,
'description' => 'When using project API key, make sure to pass x-appwrite-project header with your project ID.',
'code' => 403,
],
];
+21 -38
View File
@@ -50,17 +50,12 @@
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('https://assets.appwrite.io/fonts/poppins/poppins-v23-latin-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
</style>
<style>
body {
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
padding: 32px;
line-height: 1.5;
color: #616b7c;
@@ -68,15 +63,12 @@
font-weight: 400;
font-family: "Inter", sans-serif;
background-color: #ffffff;
margin: 0;
padding: 0;
line-height: 150%;
}
a {
.main a {
color: currentColor;
word-break: break-all;
}
a.button {
.main a.button {
box-sizing: border-box;
display: inline-block;
text-align: center;
@@ -87,8 +79,8 @@
border: 1px solid #414146;
border-radius: 8px;
}
a.button:hover,
a.button:focus {
.main a.button:hover,
.main a.button:focus {
opacity: 0.8;
}
table {
@@ -102,11 +94,6 @@
td {
vertical-align: top;
}
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
}
h1 {
font-size: 22px;
margin-bottom: 0px;
@@ -138,9 +125,6 @@
border: none;
border-top: 1px solid #e8e9f0;
}
h* {
font-family: 'Poppins', sans-serif;
}
p {
margin-bottom: 10px;
}
@@ -155,21 +139,20 @@
<body style="direction: {{direction}}">
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word;
word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
<div class="main" style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word; word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
</body>
</html>
+14
View File
@@ -2,6 +2,17 @@
use Utopia\System\System;
// For now, take first domain as primary (for previews)
// Later-on this can become platform-specific with new env var (appwrite=this,imagine=that)
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (\str_contains($sitesDomain, ',')) {
$sitesDomain = explode(',', $sitesDomain)[0];
}
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (\str_contains($functionsDomain, ',')) {
$functionsDomain = explode(',', $functionsDomain)[0];
}
/**
* Platform configuration
*/
@@ -11,6 +22,7 @@ return [
'hostnames' => array_filter(array_unique([
System::getEnv('_APP_DOMAIN', 'localhost'),
System::getEnv('_APP_CONSOLE_DOMAIN', 'localhost'),
System::getEnv('_APP_MIGRATION_HOST'),
])),
'platformName' => APP_EMAIL_PLATFORM_NAME,
'logoUrl' => APP_EMAIL_LOGO_URL,
@@ -23,4 +35,6 @@ return [
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'websiteUrl' => 'https://' . APP_DOMAIN,
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
'sitesDomain' => $sitesDomain,
'functionsDomain' => $functionsDomain,
];
+15
View File
@@ -0,0 +1,15 @@
<?php
// List of scopes for Account API keys (Tokens)
return [
"account" => [
"description" => 'Access to manage account, its organizations, sessions, tokens, and billing.',
],
"teams.read" => [
"description" => 'Access to read account\'s organizations.',
],
"teams.write" => [
"description" => 'Access to create, update and delete account\'s organizations and its memberships.',
],
];
+42
View File
@@ -0,0 +1,42 @@
<?php
// List of scopes for organization (teams) API keys
return [
"platforms.read" => [
"description" => 'Access to read project\'s platforms',
],
"platforms.write" => [
"description" =>
'Access to create, update, and delete project\'s platforms',
],
"projects.read" => [
"description" => 'Access to read organization\'s projects',
],
"projects.write" => [
"description" =>
"Access to create, update, and delete projects in organization",
],
"keys.read" => [
"description" => 'Access to read project\'s API keys',
],
"keys.write" => [
"description" =>
"Access to create, update, and delete project\'s API keys",
],
"devKeys.read" => [
"description" => 'Access to read project\'s development keys',
],
"devKeys.write" => [
"description" =>
"Access to create, update, and delete project\'s development keys",
],
"webhooks.read" => [
"description" =>
"Access to read project\'s webhooks",
],
"webhooks.write" => [
"description" =>
"Access to create, update, and delete project\'s webhooks",
],
];
+16 -16
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '21.5.0',
'version' => '22.0.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '20.3.3',
'version' => '21.0.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -79,7 +79,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '13.5.0',
'version' => '14.0.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -117,7 +117,7 @@ return [
'key' => 'android',
'name' => 'Android',
'namespace' => 'io.appwrite',
'version' => '11.4.0',
'version' => '12.0.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -140,7 +140,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.19.0',
'version' => '0.20.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -208,7 +208,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.2.0',
'version' => '0.3.0',
'url' => '',
'package' => '',
'enabled' => true,
@@ -227,7 +227,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '13.1.0',
'version' => '13.3.1',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -253,7 +253,7 @@ return [
[
'key' => 'markdown',
'name' => 'Markdown',
'version' => '0.1.0',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-md.git',
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
'enabled' => true,
@@ -283,7 +283,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '21.1.0',
'version' => '22.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -302,7 +302,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '19.1.0',
'version' => '20.0.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -321,7 +321,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '14.1.0',
'version' => '15.0.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -340,7 +340,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '20.1.0',
'version' => '21.0.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -378,7 +378,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.24.0',
'version' => '0.25.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -397,7 +397,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '20.1.1',
'version' => '21.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -417,7 +417,7 @@ return [
'key' => 'kotlin',
'name' => 'Kotlin',
'namespace' => 'io.appwrite',
'version' => '13.1.0',
'version' => '14.0.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -440,7 +440,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '14.1.0',
'version' => '15.0.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+61 -61
View File
@@ -555,7 +555,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 250,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -627,7 +627,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 252,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -751,7 +751,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 253,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -891,7 +891,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 254,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1015,7 +1015,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 258,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1149,7 +1149,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 259,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1287,7 +1287,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 251,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1388,7 +1388,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 257,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1487,7 +1487,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 255,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1586,7 +1586,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 256,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -4051,7 +4051,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 261,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4179,7 +4179,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 260,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4313,7 +4313,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 264,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4373,7 +4373,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 262,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4863,7 +4863,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 263,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4947,7 +4947,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 266,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5041,7 +5041,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 265,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5135,7 +5135,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 267,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5888,7 +5888,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 338,
"weight": 343,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -5955,7 +5955,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 334,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6026,7 +6026,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 335,
"weight": 340,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6090,7 +6090,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 336,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6168,7 +6168,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 337,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6234,7 +6234,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 339,
"weight": 344,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6319,7 +6319,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 294,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6431,7 +6431,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6592,7 +6592,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 287,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6703,7 +6703,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 290,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6858,7 +6858,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 288,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -6970,7 +6970,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 292,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7077,7 +7077,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 297,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7206,7 +7206,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 296,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7335,7 +7335,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 431,
"weight": 444,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7422,7 +7422,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 429,
"weight": 442,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7540,7 +7540,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 430,
"weight": 443,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7615,7 +7615,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 187,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7669,7 +7669,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 186,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8155,7 +8155,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 234,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8239,7 +8239,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 238,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8315,7 +8315,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 527,
"weight": 540,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8414,7 +8414,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 525,
"weight": 538,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8516,7 +8516,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 526,
"weight": 539,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8590,7 +8590,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 528,
"weight": 541,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8682,7 +8682,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 529,
"weight": 542,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8751,7 +8751,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 531,
"weight": 544,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8831,7 +8831,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 530,
"weight": 543,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9061,7 +9061,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 532,
"weight": 545,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9148,7 +9148,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 403,
"weight": 416,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9218,7 +9218,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 399,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9292,7 +9292,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 400,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9359,7 +9359,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 401,
"weight": 414,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9440,7 +9440,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 402,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9509,7 +9509,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 404,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9597,7 +9597,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 395,
"weight": 408,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9708,7 +9708,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 387,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9864,7 +9864,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 388,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9974,7 +9974,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 391,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10124,7 +10124,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 389,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10235,7 +10235,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 393,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10341,7 +10341,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 398,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10469,7 +10469,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 397,
"weight": 410,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+61 -61
View File
@@ -555,7 +555,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 250,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -627,7 +627,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 252,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -751,7 +751,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 253,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -891,7 +891,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 254,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1015,7 +1015,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 258,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1149,7 +1149,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 259,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1287,7 +1287,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 251,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1388,7 +1388,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 257,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1487,7 +1487,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 255,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1586,7 +1586,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 256,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -4051,7 +4051,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 261,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4179,7 +4179,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 260,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4313,7 +4313,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 264,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4373,7 +4373,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 262,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4863,7 +4863,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 263,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4947,7 +4947,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 266,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5041,7 +5041,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 265,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5135,7 +5135,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 267,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5888,7 +5888,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 338,
"weight": 343,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -5955,7 +5955,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 334,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6026,7 +6026,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 335,
"weight": 340,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6090,7 +6090,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 336,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6168,7 +6168,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 337,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6234,7 +6234,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 339,
"weight": 344,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6319,7 +6319,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 294,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6431,7 +6431,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6592,7 +6592,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 287,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6703,7 +6703,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 290,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6858,7 +6858,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 288,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -6970,7 +6970,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 292,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7077,7 +7077,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 297,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7206,7 +7206,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 296,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7335,7 +7335,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 431,
"weight": 444,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7422,7 +7422,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 429,
"weight": 442,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7540,7 +7540,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 430,
"weight": 443,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7615,7 +7615,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 187,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7669,7 +7669,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 186,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8155,7 +8155,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 234,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8239,7 +8239,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 238,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8315,7 +8315,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 527,
"weight": 540,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8414,7 +8414,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 525,
"weight": 538,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8516,7 +8516,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 526,
"weight": 539,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8590,7 +8590,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 528,
"weight": 541,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8682,7 +8682,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 529,
"weight": 542,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8751,7 +8751,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 531,
"weight": 544,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8831,7 +8831,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 530,
"weight": 543,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9061,7 +9061,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 532,
"weight": 545,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9148,7 +9148,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 403,
"weight": 416,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9218,7 +9218,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 399,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9292,7 +9292,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 400,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9359,7 +9359,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 401,
"weight": 414,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9440,7 +9440,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 402,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9509,7 +9509,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 404,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9597,7 +9597,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 395,
"weight": 408,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9708,7 +9708,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 387,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9864,7 +9864,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 388,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9974,7 +9974,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 391,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10124,7 +10124,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 389,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10235,7 +10235,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 393,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10341,7 +10341,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 398,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10469,7 +10469,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 397,
"weight": 410,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+61 -61
View File
@@ -612,7 +612,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 250,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -687,7 +687,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 252,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -811,7 +811,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 253,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -952,7 +952,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 254,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1076,7 +1076,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 258,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1213,7 +1213,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 259,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1353,7 +1353,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 251,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1454,7 +1454,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 257,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1555,7 +1555,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 255,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1656,7 +1656,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 256,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -4203,7 +4203,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 261,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4329,7 +4329,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 260,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4461,7 +4461,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 264,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4525,7 +4525,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 262,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -5013,7 +5013,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 263,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -5097,7 +5097,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 266,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5189,7 +5189,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 265,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5281,7 +5281,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 267,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5994,7 +5994,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 338,
"weight": 343,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -6061,7 +6061,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 334,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6132,7 +6132,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 335,
"weight": 340,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6195,7 +6195,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 336,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6274,7 +6274,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 337,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6339,7 +6339,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 339,
"weight": 344,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6420,7 +6420,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 294,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6524,7 +6524,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6683,7 +6683,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 287,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6786,7 +6786,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 290,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6937,7 +6937,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 288,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -7047,7 +7047,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 292,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7148,7 +7148,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 297,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7271,7 +7271,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 296,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7392,7 +7392,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 431,
"weight": 444,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7475,7 +7475,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 429,
"weight": 442,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7594,7 +7594,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 430,
"weight": 443,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7666,7 +7666,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 187,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7741,7 +7741,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 186,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8240,7 +8240,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 234,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8325,7 +8325,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 238,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8396,7 +8396,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 527,
"weight": 540,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8489,7 +8489,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 525,
"weight": 538,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8580,7 +8580,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 526,
"weight": 539,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8651,7 +8651,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 528,
"weight": 541,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8742,7 +8742,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 529,
"weight": 542,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8813,7 +8813,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 531,
"weight": 544,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8893,7 +8893,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 530,
"weight": 543,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9101,7 +9101,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 532,
"weight": 545,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9181,7 +9181,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 403,
"weight": 416,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9251,7 +9251,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 399,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9325,7 +9325,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 400,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9391,7 +9391,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 401,
"weight": 414,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9473,7 +9473,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 402,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9541,7 +9541,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 404,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9625,7 +9625,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 395,
"weight": 408,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9728,7 +9728,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 387,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9882,7 +9882,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 388,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9984,7 +9984,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 391,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10130,7 +10130,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 389,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10239,7 +10239,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 393,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10339,7 +10339,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 398,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10461,7 +10461,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 397,
"weight": 410,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+61 -61
View File
@@ -612,7 +612,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 250,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -687,7 +687,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 252,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -811,7 +811,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 253,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -952,7 +952,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 254,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1076,7 +1076,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 258,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1213,7 +1213,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 259,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1353,7 +1353,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 251,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1454,7 +1454,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 257,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1555,7 +1555,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 255,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1656,7 +1656,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 256,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -4203,7 +4203,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 261,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4329,7 +4329,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 260,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4461,7 +4461,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 264,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4525,7 +4525,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 262,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -5013,7 +5013,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 263,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -5097,7 +5097,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 266,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5189,7 +5189,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 265,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5281,7 +5281,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 267,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5994,7 +5994,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 338,
"weight": 343,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -6061,7 +6061,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 334,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6132,7 +6132,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 335,
"weight": 340,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6195,7 +6195,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 336,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6274,7 +6274,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 337,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6339,7 +6339,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 339,
"weight": 344,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6420,7 +6420,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 294,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6524,7 +6524,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 286,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6683,7 +6683,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 287,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6786,7 +6786,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 290,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6937,7 +6937,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 288,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -7047,7 +7047,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 292,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7148,7 +7148,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 297,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7271,7 +7271,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 296,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7392,7 +7392,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 431,
"weight": 444,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7475,7 +7475,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 429,
"weight": 442,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7594,7 +7594,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 430,
"weight": 443,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7666,7 +7666,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 187,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7741,7 +7741,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 186,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8240,7 +8240,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 234,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8325,7 +8325,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 238,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8396,7 +8396,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 527,
"weight": 540,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8489,7 +8489,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 525,
"weight": 538,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8580,7 +8580,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 526,
"weight": 539,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8651,7 +8651,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 528,
"weight": 541,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8742,7 +8742,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 529,
"weight": 542,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8813,7 +8813,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 531,
"weight": 544,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8893,7 +8893,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 530,
"weight": 543,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9101,7 +9101,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 532,
"weight": 545,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9181,7 +9181,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 403,
"weight": 416,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9251,7 +9251,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 399,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9325,7 +9325,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 400,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9391,7 +9391,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 401,
"weight": 414,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9473,7 +9473,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 402,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9541,7 +9541,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 404,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9625,7 +9625,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 395,
"weight": 408,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9728,7 +9728,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 387,
"weight": 400,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9882,7 +9882,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 388,
"weight": 401,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9984,7 +9984,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 391,
"weight": 404,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10130,7 +10130,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 389,
"weight": 402,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10239,7 +10239,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 393,
"weight": 406,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10339,7 +10339,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 398,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10461,7 +10461,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 397,
"weight": 410,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+68 -40
View File
@@ -20,6 +20,34 @@ function getRuntimes($runtimes, $commands, $entrypoint, $providerRootDirectory,
}));
}
class FunctionUseCases
{
public const STARTER = 'starter';
public const DATABASES = 'databases';
public const AI = 'ai';
public const MESSAGING = 'messaging';
public const UTILITIES = 'utilities';
public const DEV_TOOLS = 'dev-tools';
public const AUTH = 'auth';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::STARTER,
self::DATABASES,
self::AI,
self::MESSAGING,
self::UTILITIES,
self::DEV_TOOLS,
self::AUTH,
];
}
}
return [
[
'icon' => 'icon-lightning-bolt',
@@ -32,7 +60,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['starter'],
'useCases' => [FunctionUseCases::STARTER],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter', $allowList),
...getRuntimes(
@@ -73,7 +101,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -119,7 +147,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -164,7 +192,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -218,7 +246,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -257,7 +285,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -326,7 +354,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -392,7 +420,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['messaging'],
'useCases' => [FunctionUseCases::MESSAGING],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -459,7 +487,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -497,7 +525,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -548,7 +576,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf', $allowList)
],
@@ -571,7 +599,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['dev-tools'],
'useCases' => [FunctionUseCases::DEV_TOOLS],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -616,7 +644,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -669,7 +697,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -754,7 +782,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['databases'],
'useCases' => [FunctionUseCases::DATABASES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -853,7 +881,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['messaging'],
'useCases' => [FunctionUseCases::MESSAGING],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -946,7 +974,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['messaging'],
'useCases' => [FunctionUseCases::MESSAGING],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1004,7 +1032,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1091,7 +1119,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1134,7 +1162,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1193,7 +1221,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1229,7 +1257,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1265,7 +1293,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1325,7 +1353,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1385,7 +1413,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1448,7 +1476,7 @@ return [
],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1508,7 +1536,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1545,7 +1573,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1589,7 +1617,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1632,7 +1660,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1669,7 +1697,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1734,7 +1762,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1799,7 +1827,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1856,7 +1884,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1899,7 +1927,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1942,7 +1970,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1986,7 +2014,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => ['ai'],
'useCases' => [FunctionUseCases::AI],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2023,7 +2051,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2080,7 +2108,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['utilities'],
'useCases' => [FunctionUseCases::UTILITIES],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2153,7 +2181,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => ['auth'],
'useCases' => [FunctionUseCases::AUTH],
'runtimes' => [
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple', $allowList)
],
+96 -78
View File
@@ -13,7 +13,7 @@ $hostname = $platform['consoleHostname'] ?? '';
$url = $protocol . '://' . $hostname;
class UseCases
class SiteUseCases
{
public const PORTFOLIO = 'portfolio';
public const STARTER = 'starter';
@@ -21,9 +21,27 @@ class UseCases
public const ECOMMERCE = 'ecommerce';
public const DOCUMENTATION = 'documentation';
public const BLOG = 'blog';
public const AI = 'artificial intelligence';
public const AI = 'ai';
public const FORMS = 'forms';
public const DASHBOARD = 'dashboard';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::PORTFOLIO,
self::STARTER,
self::EVENTS,
self::ECOMMERCE,
self::DOCUMENTATION,
self::BLOG,
self::AI,
self::FORMS,
self::DASHBOARD,
];
}
}
const TEMPLATE_FRAMEWORKS = [
@@ -188,7 +206,7 @@ return [
'name' => 'Documentation template',
'tagline' => 'Modern site to store your knowledge with a clean design, full-text search, dark mode, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/template-for-documentation-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-documentation-light.png',
'frameworks' => [
@@ -209,7 +227,7 @@ return [
// When we add Lynx with Appwrite SDK, use following tagline for it:
// 'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-lynx-light.png',
'frameworks' => [
@@ -220,7 +238,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -228,7 +246,7 @@ return [
'name' => 'Vitepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vite.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vitepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vitepress-light.png',
'frameworks' => [
@@ -243,7 +261,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -251,7 +269,7 @@ return [
'name' => 'Vuepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vue.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vuepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vuepress-light.png',
'frameworks' => [
@@ -266,7 +284,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -274,7 +292,7 @@ return [
'name' => 'Docusaurus',
'tagline' => 'Platform for documentation and knowledge sharing powered by React.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/docusaurus-dark.png',
'screenshotLight' => $url . '/images/sites/templates/docusaurus-light.png',
'frameworks' => [
@@ -289,7 +307,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -297,7 +315,7 @@ return [
'name' => 'Nxt Lnk',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/nxt-lnk-dark.png',
'screenshotLight' => $url . '/images/sites/templates/nxt-lnk-light.png',
'frameworks' => [
@@ -308,7 +326,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -316,7 +334,7 @@ return [
'name' => 'Magic Portfolio',
'tagline' => 'Complex personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/magic-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/magic-portfolio-light.png',
'frameworks' => [
@@ -327,7 +345,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -335,7 +353,7 @@ return [
'name' => 'LittleLink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/littlelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/littlelink-light.png',
'frameworks' => [
@@ -346,7 +364,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -354,7 +372,7 @@ return [
'name' => 'Logspot',
'tagline' => 'Website to publish changelogs of your application.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'useCases' => [SiteUseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/logspot-dark.png',
'screenshotLight' => $url . '/images/sites/templates/logspot-light.png',
'frameworks' => [
@@ -368,7 +386,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -376,7 +394,7 @@ return [
'name' => 'Astro Nano',
'tagline' => 'Minimal personal website to showcase your projects, articles, and more.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-nano-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-nano-light.png',
'frameworks' => [
@@ -389,7 +407,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -397,7 +415,7 @@ return [
'name' => 'Astro Starlight',
'tagline' => 'Platform for documentation and knowledge sharing powered by Astro.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'useCases' => [SiteUseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/astro-starlight-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlight-light.png',
'frameworks' => [
@@ -410,7 +428,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -418,7 +436,7 @@ return [
'name' => 'Astro Sphere',
'tagline' => 'Modern personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-sphere-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-sphere-light.png',
'frameworks' => [
@@ -431,7 +449,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -439,7 +457,7 @@ return [
'name' => 'Astro Starlog',
'tagline' => 'Platform for publishing written content and media powered by Astro.',
'score' => 5, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'useCases' => [SiteUseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/astro-starlog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlog-light.png',
'frameworks' => [
@@ -452,7 +470,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -460,7 +478,7 @@ return [
'name' => 'Onelink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/onelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/onelink-light.png',
'frameworks' => [
@@ -474,13 +492,13 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
'key' => 'starter-for-flutter',
'name' => 'Flutter starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Flutter application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-flutter-dark.png',
@@ -525,7 +543,7 @@ return [
[
'key' => 'starter-for-js',
'name' => 'JavaScript starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple JavaScript application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-js-dark.png',
@@ -569,7 +587,7 @@ return [
[
'key' => 'starter-for-angular',
'name' => 'Angular starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Angular application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-angular-dark.png',
@@ -615,7 +633,7 @@ return [
[
'key' => 'starter-for-astro',
'name' => 'Astro starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Astro application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-astro-dark.png',
@@ -660,7 +678,7 @@ return [
[
'key' => 'starter-for-analog',
'name' => 'Analog starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Analog application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-analog-dark.png',
@@ -704,7 +722,7 @@ return [
[
'key' => 'starter-for-remix',
'name' => 'Remix starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Remix application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-remix-dark.png',
@@ -748,7 +766,7 @@ return [
[
'key' => 'starter-for-svelte',
'name' => 'Svelte starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Svelte application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png',
@@ -792,7 +810,7 @@ return [
[
'key' => 'starter-for-react',
'name' => 'React starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple React application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png',
@@ -836,7 +854,7 @@ return [
[
'key' => 'starter-for-vue',
'name' => 'Vue starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Vue application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png',
@@ -880,7 +898,7 @@ return [
[
'key' => 'starter-for-react-native',
'name' => 'React Native starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple React Native application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
@@ -924,7 +942,7 @@ return [
[
'key' => 'starter-for-nextjs',
'name' => 'Next.js starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Next.js application integrated with Appwrite SDK.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nextjs-dark.png',
@@ -968,7 +986,7 @@ return [
[
'key' => 'starter-for-tanstack-start',
'name' => 'TanStack Start starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.',
'score' => 9, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png',
@@ -1012,7 +1030,7 @@ return [
[
'key' => 'starter-for-nuxt',
'name' => 'Nuxt starter',
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'tagline' => 'Simple Nuxt application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png',
@@ -1058,7 +1076,7 @@ return [
'name' => 'Event template',
'tagline' => 'Hackathon landing page with support for project submissions.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::EVENTS],
'useCases' => [SiteUseCases::EVENTS],
'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png',
'frameworks' => [
@@ -1096,7 +1114,7 @@ return [
'name' => 'Portfolio template',
'tagline' => 'Simple personal website to showcase your projects, articles, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'useCases' => [SiteUseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png',
'frameworks' => [
@@ -1115,7 +1133,7 @@ return [
'name' => 'Store template',
'tagline' => 'E-commerce platform for selling products with Stripe integration.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::ECOMMERCE],
'useCases' => [SiteUseCases::ECOMMERCE],
'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png',
'frameworks' => [
@@ -1159,7 +1177,7 @@ return [
'name' => 'Blog template',
'tagline' => 'Platform for publishing written content and media.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'useCases' => [SiteUseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png',
'frameworks' => [
@@ -1178,7 +1196,7 @@ return [
'name' => 'Astro playground',
'tagline' => 'A basic Astro website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-astro-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-astro-light.png',
'frameworks' => [
@@ -1189,7 +1207,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1197,7 +1215,7 @@ return [
'name' => 'Remix playground',
'tagline' => 'A basic Remix website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-remix-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-remix-light.png',
'frameworks' => [
@@ -1208,7 +1226,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1216,7 +1234,7 @@ return [
'name' => 'Next.js playground',
'tagline' => 'A basic Next.js website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nextjs-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nextjs-light.png',
'frameworks' => [
@@ -1227,7 +1245,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1235,7 +1253,7 @@ return [
'name' => 'Flutter playground',
'tagline' => 'A basic Flutter website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-flutter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-flutter-light.png',
'frameworks' => [
@@ -1246,7 +1264,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1254,7 +1272,7 @@ return [
'name' => 'Vite playground',
'tagline' => 'A basic Vite website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vite-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vite-light.png',
'frameworks' => [
@@ -1265,7 +1283,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1273,7 +1291,7 @@ return [
'name' => 'Angular playground',
'tagline' => 'A basic Angular website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-angular-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-angular-light.png',
'frameworks' => [
@@ -1285,7 +1303,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1293,7 +1311,7 @@ return [
'name' => 'Analog playground',
'tagline' => 'A basic Analog website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-analog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-analog-light.png',
'frameworks' => [
@@ -1304,7 +1322,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1312,7 +1330,7 @@ return [
'name' => 'Svelte playground',
'tagline' => 'A basic Svelte website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-svelte-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-svelte-light.png',
'frameworks' => [
@@ -1323,7 +1341,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
@@ -1332,7 +1350,7 @@ return [
'name' => 'React playground',
'tagline' => 'A basic React website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-light.png',
'frameworks' => [
@@ -1344,7 +1362,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
@@ -1353,7 +1371,7 @@ return [
'name' => 'Vue playground',
'tagline' => 'A basic Vue website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vue-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vue-light.png',
'frameworks' => [
@@ -1364,7 +1382,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1372,7 +1390,7 @@ return [
'name' => 'Nuxt playground',
'tagline' => 'A basic Nuxt website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nuxt-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nuxt-light.png',
'frameworks' => [
@@ -1383,7 +1401,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1391,7 +1409,7 @@ return [
'name' => 'TanStack Start playground',
'tagline' => 'A basic TanStack Start website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-tanstack-start-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-tanstack-start-light.png',
'frameworks' => [
@@ -1402,7 +1420,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.5.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1410,7 +1428,7 @@ return [
'name' => 'React Native playground',
'tagline' => 'A basic React Native website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-native-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-native-light.png',
'frameworks' => [
@@ -1421,7 +1439,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => [],
],
[
@@ -1429,7 +1447,7 @@ return [
'name' => 'Lynx gallery',
'tagline' => 'A Lynx website showcasing gallery with smooth animations.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'useCases' => [SiteUseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/gallery-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/gallery-for-lynx-light.png',
'frameworks' => [
@@ -1440,7 +1458,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'providerVersion' => '0.7.*',
'variables' => []
],
[
@@ -1448,7 +1466,7 @@ return [
'name' => 'Text-to-speech with ElevenLabs',
'tagline' => 'Next.js app that transforms text into natural, human-like speech using ElevenLabs',
'score' => 10, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::AI],
'useCases' => [SiteUseCases::AI],
'screenshotDark' => $url . '/images/sites/templates/text-to-speech-dark.png',
'screenshotLight' => $url . '/images/sites/templates/text-to-speech-light.png',
'frameworks' => [
@@ -1459,7 +1477,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.6.*',
'providerVersion' => '0.7.*',
'variables' => [
[
'name' => 'ELEVENLABS_API_KEY',
@@ -1476,7 +1494,7 @@ return [
'name' => 'CRM dashboard with React Admin',
'tagline' => 'A React-based admin dashboard template with CRM features.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DASHBOARD],
'useCases' => [SiteUseCases::DASHBOARD],
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
'frameworks' => [
@@ -1579,7 +1597,7 @@ return [
'name' => 'Job applications form with Formspree',
'tagline' => 'A simple form submission template using Formspree.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::FORMS],
'useCases' => [SiteUseCases::FORMS],
'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png',
'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png',
'frameworks' => [
+71 -57
View File
@@ -36,7 +36,6 @@ use Appwrite\Utopia\Response;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code as ProofsCode;
@@ -61,6 +60,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\Storage\Validator\FileName;
use Utopia\System\System;
@@ -195,7 +195,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, arr
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@@ -344,7 +344,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
$response->dynamic($session, Response::MODEL_SESSION);
};
App::post('/v1/account')
Http::post('/v1/account')
->desc('Create account')
->groups(['api', 'account', 'auth'])
->label('scope', 'sessions.write')
@@ -507,7 +507,7 @@ App::post('/v1/account')
->dynamic($user, Response::MODEL_ACCOUNT);
});
App::get('/v1/account')
Http::get('/v1/account')
->desc('Get account')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -535,7 +535,7 @@ App::get('/v1/account')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::delete('/v1/account')
Http::delete('/v1/account')
->desc('Delete account')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -590,7 +590,7 @@ App::delete('/v1/account')
$response->noContent();
});
App::get('/v1/account/sessions')
Http::get('/v1/account/sessions')
->desc('List sessions')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -635,7 +635,7 @@ App::get('/v1/account/sessions')
]), Response::MODEL_SESSION_LIST);
});
App::delete('/v1/account/sessions')
Http::delete('/v1/account/sessions')
->desc('Delete sessions')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -710,7 +710,7 @@ App::delete('/v1/account/sessions')
$response->noContent();
});
App::get('/v1/account/sessions/:sessionId')
Http::get('/v1/account/sessions/:sessionId')
->desc('Get session')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -758,7 +758,7 @@ App::get('/v1/account/sessions/:sessionId')
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
});
App::delete('/v1/account/sessions/:sessionId')
Http::delete('/v1/account/sessions/:sessionId')
->desc('Delete session')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
@@ -845,7 +845,7 @@ App::delete('/v1/account/sessions/:sessionId')
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
});
App::patch('/v1/account/sessions/:sessionId')
Http::patch('/v1/account/sessions/:sessionId')
->desc('Update session')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -934,7 +934,7 @@ App::patch('/v1/account/sessions/:sessionId')
return $response->dynamic($session, Response::MODEL_SESSION);
});
App::post('/v1/account/sessions/email')
Http::post('/v1/account/sessions/email')
->alias('/v1/account/sessions')
->desc('Create email password session')
->groups(['api', 'account', 'auth', 'session'])
@@ -1086,7 +1086,7 @@ App::post('/v1/account/sessions/email')
$response->dynamic($session, Response::MODEL_SESSION);
});
App::post('/v1/account/sessions/anonymous')
Http::post('/v1/account/sessions/anonymous')
->desc('Create anonymous session')
->groups(['api', 'account', 'auth', 'session'])
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -1237,7 +1237,7 @@ App::post('/v1/account/sessions/anonymous')
$response->dynamic($session, Response::MODEL_SESSION);
});
App::post('/v1/account/sessions/token')
Http::post('/v1/account/sessions/token')
->desc('Create session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
@@ -1280,7 +1280,7 @@ App::post('/v1/account/sessions/token')
->inject('authorization')
->action($createSession);
App::get('/v1/account/sessions/oauth2/:provider')
Http::get('/v1/account/sessions/oauth2/:provider')
->desc('Create OAuth2 session')
->groups(['api', 'account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1374,7 +1374,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
->redirect($oauth2->getLoginURL());
});
App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
Http::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->desc('Get OAuth2 callback')
->groups(['account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1409,7 +1409,7 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
. \http_build_query($params));
});
App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
Http::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->desc('Create OAuth2 callback')
->groups(['account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1445,7 +1445,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
. \http_build_query($params));
});
App::get('/v1/account/sessions/oauth2/:provider/redirect')
Http::get('/v1/account/sessions/oauth2/:provider/redirect')
->desc('Get OAuth2 redirect')
->groups(['api', 'account', 'session'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1469,13 +1469,14 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('devKey')
->inject('user')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('geodb')
->inject('queueForEvents')
->inject('store')
->inject('proofForPassword')
->inject('proofForToken')
->inject('authorization')
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) use ($oauthDefaultSuccess) {
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Database $dbForPlatform, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) use ($oauthDefaultSuccess) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
@@ -1512,6 +1513,25 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$state = $defaultState;
}
// Allow redirect to rule URL if related to project
//Check if $redirectValidator is instance of Redirect class
if ($redirectValidator instanceof Redirect) {
$rules = $authorization->skip(fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [
parse_url($state['success'], PHP_URL_HOST),
parse_url($state['failure'], PHP_URL_HOST)
]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::limit(2)
]));
foreach ($rules as $rule) {
$allowedHostnames = $redirectValidator->getAllowedHostnames();
$allowedHostnames[] = $rule->getAttribute('domain', '');
$redirectValidator->setAllowedHostnames($allowedHostnames);
}
}
if ($devKey->isEmpty() && !$redirectValidator->isValid($state['success'])) {
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
}
@@ -1948,7 +1968,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
;
});
App::get('/v1/account/tokens/oauth2/:provider')
Http::get('/v1/account/tokens/oauth2/:provider')
->desc('Create OAuth2 token')
->groups(['api', 'account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -2043,7 +2063,7 @@ App::get('/v1/account/tokens/oauth2/:provider')
->redirect($oauth2->getLoginURL());
});
App::post('/v1/account/tokens/magic-url')
Http::post('/v1/account/tokens/magic-url')
->alias('/v1/account/sessions/magic-url')
->desc('Create magic URL token')
->groups(['api', 'account', 'auth'])
@@ -2301,7 +2321,7 @@ App::post('/v1/account/tokens/magic-url')
->setSubject($subject)
->setPreview($preview)
->setBody($body)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
if ($project->getId() === 'console') {
@@ -2324,7 +2344,7 @@ App::post('/v1/account/tokens/magic-url')
->dynamic($token, Response::MODEL_TOKEN);
});
App::post('/v1/account/tokens/email')
Http::post('/v1/account/tokens/email')
->desc('Create email token (OTP)')
->groups(['api', 'account', 'auth'])
->label('scope', 'sessions.write')
@@ -2602,7 +2622,7 @@ App::post('/v1/account/tokens/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@@ -2626,7 +2646,7 @@ App::post('/v1/account/tokens/email')
->dynamic($token, Response::MODEL_TOKEN);
});
App::put('/v1/account/sessions/magic-url')
Http::put('/v1/account/sessions/magic-url')
->desc('Update magic URL session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
@@ -2676,7 +2696,7 @@ App::put('/v1/account/sessions/magic-url')
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $authorization);
});
App::put('/v1/account/sessions/phone')
Http::put('/v1/account/sessions/phone')
->desc('Update phone session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
@@ -2722,7 +2742,7 @@ App::put('/v1/account/sessions/phone')
->inject('authorization')
->action($createSession);
App::post('/v1/account/tokens/phone')
Http::post('/v1/account/tokens/phone')
->alias('/v1/account/sessions/phone')
->desc('Create phone token')
->groups(['api', 'account', 'auth'])
@@ -2942,7 +2962,7 @@ App::post('/v1/account/tokens/phone')
->dynamic($token, Response::MODEL_TOKEN);
});
App::post('/v1/account/jwts')
Http::post('/v1/account/jwts')
->alias('/v1/account/jwt')
->desc('Create JWT')
->groups(['api', 'account', 'auth'])
@@ -2989,7 +3009,7 @@ App::post('/v1/account/jwts')
]), Response::MODEL_JWT);
});
App::get('/v1/account/prefs')
Http::get('/v1/account/prefs')
->desc('Get account preferences')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -3016,7 +3036,7 @@ App::get('/v1/account/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
App::get('/v1/account/logs')
Http::get('/v1/account/logs')
->desc('List logs')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -3087,7 +3107,7 @@ App::get('/v1/account/logs')
]), Response::MODEL_LOG_LIST);
});
App::patch('/v1/account/name')
Http::patch('/v1/account/name')
->desc('Update name')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.name')
@@ -3124,7 +3144,7 @@ App::patch('/v1/account/name')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/password')
Http::patch('/v1/account/password')
->desc('Update password')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.password')
@@ -3217,7 +3237,7 @@ App::patch('/v1/account/password')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/email')
Http::patch('/v1/account/email')
->desc('Update email')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.email')
@@ -3329,7 +3349,7 @@ App::patch('/v1/account/email')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/phone')
Http::patch('/v1/account/phone')
->desc('Update phone')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.phone')
@@ -3418,7 +3438,7 @@ App::patch('/v1/account/phone')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/prefs')
Http::patch('/v1/account/prefs')
->desc('Update preferences')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.prefs')
@@ -3456,7 +3476,7 @@ App::patch('/v1/account/prefs')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::patch('/v1/account/status')
Http::patch('/v1/account/status')
->desc('Update status')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.status')
@@ -3506,7 +3526,7 @@ App::patch('/v1/account/status')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
App::post('/v1/account/recovery')
Http::post('/v1/account/recovery')
->desc('Create password recovery')
->groups(['api', 'account'])
->label('scope', 'sessions.write')
@@ -3678,7 +3698,7 @@ App::post('/v1/account/recovery')
->setRecipient($profile->getAttribute('email', ''))
->setName($profile->getAttribute('name', ''))
->setBody($body)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setSubject($subject)
->setPreview($preview);
@@ -3701,7 +3721,7 @@ App::post('/v1/account/recovery')
->dynamic($recovery, Response::MODEL_TOKEN);
});
App::put('/v1/account/recovery')
Http::put('/v1/account/recovery')
->desc('Update password recovery (confirmation)')
->groups(['api', 'account'])
->label('scope', 'sessions.write')
@@ -3798,7 +3818,7 @@ App::put('/v1/account/recovery')
$response->dynamic($recoveryDocument, Response::MODEL_TOKEN);
});
App::post('/v1/account/verifications/email')
Http::post('/v1/account/verifications/email')
->alias('/v1/account/verification')
->desc('Create email verification')
->groups(['api', 'account'])
@@ -4009,7 +4029,7 @@ App::post('/v1/account/verifications/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name') ?? '');
@@ -4031,7 +4051,7 @@ App::post('/v1/account/verifications/email')
->dynamic($verification, Response::MODEL_TOKEN);
});
App::put('/v1/account/verifications/email')
Http::put('/v1/account/verifications/email')
->alias('/v1/account/verification')
->desc('Update email verification (confirmation)')
->groups(['api', 'account'])
@@ -4121,7 +4141,7 @@ App::put('/v1/account/verifications/email')
$response->dynamic($verification, Response::MODEL_TOKEN);
});
App::post('/v1/account/verifications/phone')
Http::post('/v1/account/verifications/phone')
->alias('/v1/account/verification/phone')
->desc('Create phone verification')
->groups(['api', 'account', 'auth'])
@@ -4268,7 +4288,7 @@ App::post('/v1/account/verifications/phone')
->dynamic($verification, Response::MODEL_TOKEN);
});
App::put('/v1/account/verifications/phone')
Http::put('/v1/account/verifications/phone')
->alias('/v1/account/verification/phone')
->desc('Update phone verification (confirmation)')
->groups(['api', 'account'])
@@ -4336,7 +4356,7 @@ App::put('/v1/account/verifications/phone')
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
});
App::post('/v1/account/targets/push')
Http::post('/v1/account/targets/push')
->desc('Create push target')
->groups(['api', 'account'])
->label('scope', 'targets.write')
@@ -4420,7 +4440,7 @@ App::post('/v1/account/targets/push')
->dynamic($target, Response::MODEL_TARGET);
});
App::put('/v1/account/targets/:targetId/push')
Http::put('/v1/account/targets/:targetId/push')
->desc('Update push target')
->groups(['api', 'account'])
->label('scope', 'targets.write')
@@ -4486,7 +4506,7 @@ App::put('/v1/account/targets/:targetId/push')
->dynamic($target, Response::MODEL_TARGET);
});
App::delete('/v1/account/targets/:targetId/push')
Http::delete('/v1/account/targets/:targetId/push')
->desc('Delete push target')
->groups(['api', 'account'])
->label('scope', 'targets.write')
@@ -4541,7 +4561,7 @@ App::delete('/v1/account/targets/:targetId/push')
$response->noContent();
});
App::get('/v1/account/identities')
Http::get('/v1/account/identities')
->desc('List identities')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -4574,16 +4594,10 @@ App::get('/v1/account/identities')
$queries[] = Query::equal('userInternalId', [$user->getSequence()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -4613,7 +4627,7 @@ App::get('/v1/account/identities')
]), Response::MODEL_IDENTITY_LIST);
});
App::delete('/v1/account/identities/:identityId')
Http::delete('/v1/account/identities/:identityId')
->desc('Delete identity')
->groups(['api', 'account'])
->label('scope', 'account')
+7 -7
View File
@@ -18,14 +18,14 @@ use GraphQL\Validator\Rules\DisableIntrospection;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\QueryDepth;
use Swoole\Coroutine\WaitGroup;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Http;
use Utopia\System\System;
use Utopia\Validator\JSON;
use Utopia\Validator\Text;
App::init()
Http::init()
->groups(['graphql'])
->inject('project')
->inject('authorization')
@@ -39,7 +39,7 @@ App::init()
}
});
App::get('/v1/graphql')
Http::get('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -86,7 +86,7 @@ App::get('/v1/graphql')
->json($output);
});
App::post('/v1/graphql/mutation')
Http::post('/v1/graphql/mutation')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -137,7 +137,7 @@ App::post('/v1/graphql/mutation')
->json($output);
});
App::post('/v1/graphql')
Http::post('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -232,7 +232,7 @@ function execute(
$validations[] = new QueryComplexity($maxComplexity);
$validations[] = new QueryDepth($maxDepth);
}
if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
if (Http::getMode() === Http::MODE_TYPE_PRODUCTION) {
$flags = DebugFlag::NONE;
}
@@ -333,7 +333,7 @@ function processResult($result, $debugFlags): array
);
}
App::shutdown()
Http::shutdown()
->groups(['schema'])
->inject('project')
->action(function (Document $project) {
+9 -9
View File
@@ -6,12 +6,12 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Http;
use Utopia\Locale\Locale;
App::get('/v1/locale')
Http::get('/v1/locale')
->desc('Get user locale')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -70,7 +70,7 @@ App::get('/v1/locale')
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
});
App::get('/v1/locale/codes')
Http::get('/v1/locale/codes')
->desc('List locale codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -96,7 +96,7 @@ App::get('/v1/locale/codes')
]), Response::MODEL_LOCALE_CODE_LIST);
});
App::get('/v1/locale/countries')
Http::get('/v1/locale/countries')
->desc('List countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -133,7 +133,7 @@ App::get('/v1/locale/countries')
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
});
App::get('/v1/locale/countries/eu')
Http::get('/v1/locale/countries/eu')
->desc('List EU countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -172,7 +172,7 @@ App::get('/v1/locale/countries/eu')
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
});
App::get('/v1/locale/countries/phones')
Http::get('/v1/locale/countries/phones')
->desc('List countries phone codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -210,7 +210,7 @@ App::get('/v1/locale/countries/phones')
$response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST);
});
App::get('/v1/locale/continents')
Http::get('/v1/locale/continents')
->desc('List continents')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -246,7 +246,7 @@ App::get('/v1/locale/continents')
$response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST);
});
App::get('/v1/locale/currencies')
Http::get('/v1/locale/currencies')
->desc('List currencies')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -273,7 +273,7 @@ App::get('/v1/locale/currencies')
});
App::get('/v1/locale/languages')
Http::get('/v1/locale/languages')
->desc('List languages')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
+64 -89
View File
@@ -25,7 +25,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Appwrite\Utopia\Database\Validator\Queries\Topics;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -44,6 +43,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -57,7 +57,7 @@ use Utopia\Validator\WhiteList;
use function Swoole\Coroutine\batch;
App::post('/v1/messaging/providers/mailgun')
Http::post('/v1/messaging/providers/mailgun')
->desc('Create Mailgun provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -151,7 +151,7 @@ App::post('/v1/messaging/providers/mailgun')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/sendgrid')
Http::post('/v1/messaging/providers/sendgrid')
->desc('Create Sendgrid provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -233,7 +233,7 @@ App::post('/v1/messaging/providers/sendgrid')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/resend')
Http::post('/v1/messaging/providers/resend')
->desc('Create Resend provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -315,7 +315,7 @@ App::post('/v1/messaging/providers/resend')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/smtp')
Http::post('/v1/messaging/providers/smtp')
->desc('Create SMTP provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -430,7 +430,7 @@ App::post('/v1/messaging/providers/smtp')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/msg91')
Http::post('/v1/messaging/providers/msg91')
->desc('Create Msg91 provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -513,7 +513,7 @@ App::post('/v1/messaging/providers/msg91')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/telesign')
Http::post('/v1/messaging/providers/telesign')
->desc('Create Telesign provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -597,7 +597,7 @@ App::post('/v1/messaging/providers/telesign')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/textmagic')
Http::post('/v1/messaging/providers/textmagic')
->desc('Create Textmagic provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -681,7 +681,7 @@ App::post('/v1/messaging/providers/textmagic')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/twilio')
Http::post('/v1/messaging/providers/twilio')
->desc('Create Twilio provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -765,7 +765,7 @@ App::post('/v1/messaging/providers/twilio')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/vonage')
Http::post('/v1/messaging/providers/vonage')
->desc('Create Vonage provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -849,7 +849,7 @@ App::post('/v1/messaging/providers/vonage')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/fcm')
Http::post('/v1/messaging/providers/fcm')
->desc('Create FCM provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -939,7 +939,7 @@ App::post('/v1/messaging/providers/fcm')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::post('/v1/messaging/providers/apns')
Http::post('/v1/messaging/providers/apns')
->desc('Create APNS provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -1052,7 +1052,7 @@ App::post('/v1/messaging/providers/apns')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::get('/v1/messaging/providers')
Http::get('/v1/messaging/providers')
->desc('List providers')
->groups(['api', 'messaging'])
->label('scope', 'providers.read')
@@ -1087,15 +1087,10 @@ App::get('/v1/messaging/providers')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -1122,7 +1117,7 @@ App::get('/v1/messaging/providers')
]), Response::MODEL_PROVIDER_LIST);
});
App::get('/v1/messaging/providers/:providerId/logs')
Http::get('/v1/messaging/providers/:providerId/logs')
->desc('List provider logs')
->groups(['api', 'messaging'])
->label('scope', 'providers.read')
@@ -1218,7 +1213,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
]), Response::MODEL_LOG_LIST);
});
App::get('/v1/messaging/providers/:providerId')
Http::get('/v1/messaging/providers/:providerId')
->desc('Get provider')
->groups(['api', 'messaging'])
->label('scope', 'providers.read')
@@ -1249,7 +1244,7 @@ App::get('/v1/messaging/providers/:providerId')
$response->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/mailgun/:providerId')
Http::patch('/v1/messaging/providers/mailgun/:providerId')
->desc('Update Mailgun provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1362,7 +1357,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/sendgrid/:providerId')
Http::patch('/v1/messaging/providers/sendgrid/:providerId')
->desc('Update Sendgrid provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1460,7 +1455,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/resend/:providerId')
Http::patch('/v1/messaging/providers/resend/:providerId')
->desc('Update Resend provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1558,7 +1553,7 @@ App::patch('/v1/messaging/providers/resend/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/smtp/:providerId')
Http::patch('/v1/messaging/providers/smtp/:providerId')
->desc('Update SMTP provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1707,7 +1702,7 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/msg91/:providerId')
Http::patch('/v1/messaging/providers/msg91/:providerId')
->desc('Update Msg91 provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1794,7 +1789,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/telesign/:providerId')
Http::patch('/v1/messaging/providers/telesign/:providerId')
->desc('Update Telesign provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1883,7 +1878,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/textmagic/:providerId')
Http::patch('/v1/messaging/providers/textmagic/:providerId')
->desc('Update Textmagic provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1972,7 +1967,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/twilio/:providerId')
Http::patch('/v1/messaging/providers/twilio/:providerId')
->desc('Update Twilio provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -2061,7 +2056,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/vonage/:providerId')
Http::patch('/v1/messaging/providers/vonage/:providerId')
->desc('Update Vonage provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -2150,7 +2145,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::patch('/v1/messaging/providers/fcm/:providerId')
Http::patch('/v1/messaging/providers/fcm/:providerId')
->desc('Update FCM provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -2246,7 +2241,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
});
App::patch('/v1/messaging/providers/apns/:providerId')
Http::patch('/v1/messaging/providers/apns/:providerId')
->desc('Update APNS provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -2368,7 +2363,7 @@ App::patch('/v1/messaging/providers/apns/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
App::delete('/v1/messaging/providers/:providerId')
Http::delete('/v1/messaging/providers/:providerId')
->desc('Delete provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.delete')
@@ -2411,7 +2406,7 @@ App::delete('/v1/messaging/providers/:providerId')
->noContent();
});
App::post('/v1/messaging/topics')
Http::post('/v1/messaging/topics')
->desc('Create topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.create')
@@ -2461,7 +2456,7 @@ App::post('/v1/messaging/topics')
->dynamic($topic, Response::MODEL_TOPIC);
});
App::get('/v1/messaging/topics')
Http::get('/v1/messaging/topics')
->desc('List topics')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
@@ -2496,15 +2491,10 @@ App::get('/v1/messaging/topics')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -2531,7 +2521,7 @@ App::get('/v1/messaging/topics')
]), Response::MODEL_TOPIC_LIST);
});
App::get('/v1/messaging/topics/:topicId/logs')
Http::get('/v1/messaging/topics/:topicId/logs')
->desc('List topic logs')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
@@ -2628,7 +2618,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
]), Response::MODEL_LOG_LIST);
});
App::get('/v1/messaging/topics/:topicId')
Http::get('/v1/messaging/topics/:topicId')
->desc('Get topic')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
@@ -2660,7 +2650,7 @@ App::get('/v1/messaging/topics/:topicId')
->dynamic($topic, Response::MODEL_TOPIC);
});
App::patch('/v1/messaging/topics/:topicId')
Http::patch('/v1/messaging/topics/:topicId')
->desc('Update topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.update')
@@ -2711,7 +2701,7 @@ App::patch('/v1/messaging/topics/:topicId')
->dynamic($topic, Response::MODEL_TOPIC);
});
App::delete('/v1/messaging/topics/:topicId')
Http::delete('/v1/messaging/topics/:topicId')
->desc('Delete topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.delete')
@@ -2759,7 +2749,7 @@ App::delete('/v1/messaging/topics/:topicId')
->noContent();
});
App::post('/v1/messaging/topics/:topicId/subscribers')
Http::post('/v1/messaging/topics/:topicId/subscribers')
->desc('Create subscriber')
->groups(['api', 'messaging'])
->label('audits.event', 'subscriber.create')
@@ -2860,7 +2850,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
});
App::get('/v1/messaging/topics/:topicId/subscribers')
Http::get('/v1/messaging/topics/:topicId/subscribers')
->desc('List subscribers')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
@@ -2904,15 +2894,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
$queries[] = Query::equal('topicInternalId', [$topic->getSequence()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -2951,7 +2936,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
]), Response::MODEL_SUBSCRIBER_LIST);
});
App::get('/v1/messaging/subscribers/:subscriberId/logs')
Http::get('/v1/messaging/subscribers/:subscriberId/logs')
->desc('List subscriber logs')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
@@ -3048,7 +3033,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
]), Response::MODEL_LOG_LIST);
});
App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->desc('Get subscriber')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
@@ -3095,7 +3080,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
});
App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->desc('Delete subscriber')
->groups(['api', 'messaging'])
->label('audits.event', 'subscriber.delete')
@@ -3163,7 +3148,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->noContent();
});
App::post('/v1/messaging/messages/email')
Http::post('/v1/messaging/messages/email')
->desc('Create email')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
@@ -3322,7 +3307,7 @@ App::post('/v1/messaging/messages/email')
->dynamic($message, Response::MODEL_MESSAGE);
});
App::post('/v1/messaging/messages/sms')
Http::post('/v1/messaging/messages/sms')
->desc('Create SMS')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
@@ -3465,7 +3450,7 @@ App::post('/v1/messaging/messages/sms')
->dynamic($message, Response::MODEL_MESSAGE);
});
App::post('/v1/messaging/messages/push')
Http::post('/v1/messaging/messages/push')
->desc('Create push notification')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
@@ -3684,7 +3669,7 @@ App::post('/v1/messaging/messages/push')
->dynamic($message, Response::MODEL_MESSAGE);
});
App::get('/v1/messaging/messages')
Http::get('/v1/messaging/messages')
->desc('List messages')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3719,15 +3704,10 @@ App::get('/v1/messaging/messages')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -3754,7 +3734,7 @@ App::get('/v1/messaging/messages')
]), Response::MODEL_MESSAGE_LIST);
});
App::get('/v1/messaging/messages/:messageId/logs')
Http::get('/v1/messaging/messages/:messageId/logs')
->desc('List message logs')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3851,7 +3831,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
]), Response::MODEL_LOG_LIST);
});
App::get('/v1/messaging/messages/:messageId/targets')
Http::get('/v1/messaging/messages/:messageId/targets')
->desc('List message targets')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3899,15 +3879,10 @@ App::get('/v1/messaging/messages/:messageId/targets')
$queries[] = Query::equal('$id', $targetIDs);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor) {
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -3934,7 +3909,7 @@ App::get('/v1/messaging/messages/:messageId/targets')
]), Response::MODEL_TARGET_LIST);
});
App::get('/v1/messaging/messages/:messageId')
Http::get('/v1/messaging/messages/:messageId')
->desc('Get message')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3965,7 +3940,7 @@ App::get('/v1/messaging/messages/:messageId')
$response->dynamic($message, Response::MODEL_MESSAGE);
});
App::patch('/v1/messaging/messages/email/:messageId')
Http::patch('/v1/messaging/messages/email/:messageId')
->desc('Update email')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
@@ -4172,7 +4147,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
->dynamic($message, Response::MODEL_MESSAGE);
});
App::patch('/v1/messaging/messages/sms/:messageId')
Http::patch('/v1/messaging/messages/sms/:messageId')
->desc('Update SMS')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
@@ -4354,7 +4329,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
->dynamic($message, Response::MODEL_MESSAGE);
});
App::patch('/v1/messaging/messages/push/:messageId')
Http::patch('/v1/messaging/messages/push/:messageId')
->desc('Update push notification')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
@@ -4615,7 +4590,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
->dynamic($message, Response::MODEL_MESSAGE);
});
App::delete('/v1/messaging/messages/:messageId')
Http::delete('/v1/messaging/messages/:messageId')
->desc('Delete message')
->groups(['api', 'messaging'])
->label('audits.event', 'message.delete')
+18 -24
View File
@@ -11,7 +11,6 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CompoundUID;
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
@@ -22,6 +21,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries\Documents;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\CSV;
@@ -43,7 +43,7 @@ use Utopia\Validator\WhiteList;
include_once __DIR__ . '/../shared/api.php';
App::post('/v1/migrations/appwrite')
Http::post('/v1/migrations/appwrite')
->groups(['api', 'migrations'])
->desc('Create Appwrite migration')
->label('scope', 'migrations.write')
@@ -106,7 +106,7 @@ App::post('/v1/migrations/appwrite')
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::post('/v1/migrations/firebase')
Http::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
->desc('Create Firebase migration')
->label('scope', 'migrations.write')
@@ -175,7 +175,7 @@ App::post('/v1/migrations/firebase')
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::post('/v1/migrations/supabase')
Http::post('/v1/migrations/supabase')
->groups(['api', 'migrations'])
->desc('Create Supabase migration')
->label('scope', 'migrations.write')
@@ -244,7 +244,7 @@ App::post('/v1/migrations/supabase')
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::post('/v1/migrations/nhost')
Http::post('/v1/migrations/nhost')
->groups(['api', 'migrations'])
->desc('Create NHost migration')
->label('scope', 'migrations.write')
@@ -315,7 +315,7 @@ App::post('/v1/migrations/nhost')
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::post('/v1/migrations/csv/imports')
Http::post('/v1/migrations/csv/imports')
->alias('/v1/migrations/csv')
->groups(['api', 'migrations'])
->desc('Import documents from a CSV')
@@ -461,7 +461,7 @@ App::post('/v1/migrations/csv/imports')
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::post('/v1/migrations/csv/exports')
Http::post('/v1/migrations/csv/exports')
->groups(['api', 'migrations'])
->desc('Export documents to CSV')
->label('scope', 'migrations.write')
@@ -596,7 +596,7 @@ App::post('/v1/migrations/csv/exports')
->dynamic($migration, Response::MODEL_MIGRATION);
});
App::get('/v1/migrations')
Http::get('/v1/migrations')
->groups(['api', 'migrations'])
->desc('List migrations')
->label('scope', 'migrations.read')
@@ -629,16 +629,10 @@ App::get('/v1/migrations')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -667,7 +661,7 @@ App::get('/v1/migrations')
]), Response::MODEL_MIGRATION_LIST);
});
App::get('/v1/migrations/:migrationId')
Http::get('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Get migration')
->label('scope', 'migrations.read')
@@ -697,7 +691,7 @@ App::get('/v1/migrations/:migrationId')
$response->dynamic($migration, Response::MODEL_MIGRATION);
});
App::get('/v1/migrations/appwrite/report')
Http::get('/v1/migrations/appwrite/report')
->groups(['api', 'migrations'])
->desc('Get Appwrite migration report')
->label('scope', 'migrations.write')
@@ -746,7 +740,7 @@ App::get('/v1/migrations/appwrite/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/firebase/report')
Http::get('/v1/migrations/firebase/report')
->groups(['api', 'migrations'])
->desc('Get Firebase migration report')
->label('scope', 'migrations.write')
@@ -799,7 +793,7 @@ App::get('/v1/migrations/firebase/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/supabase/report')
Http::get('/v1/migrations/supabase/report')
->groups(['api', 'migrations'])
->desc('Get Supabase migration report')
->label('scope', 'migrations.write')
@@ -848,7 +842,7 @@ App::get('/v1/migrations/supabase/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::get('/v1/migrations/nhost/report')
Http::get('/v1/migrations/nhost/report')
->groups(['api', 'migrations'])
->desc('Get NHost migration report')
->label('scope', 'migrations.write')
@@ -897,7 +891,7 @@ App::get('/v1/migrations/nhost/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
App::patch('/v1/migrations/:migrationId')
Http::patch('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Update retry migration')
->label('scope', 'migrations.write')
@@ -950,7 +944,7 @@ App::patch('/v1/migrations/:migrationId')
$response->noContent();
});
App::delete('/v1/migrations/:migrationId')
Http::delete('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Delete migration')
->label('scope', 'migrations.write')
+7 -7
View File
@@ -6,7 +6,6 @@ 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;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
@@ -17,12 +16,13 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::get('/v1/project/usage')
Http::get('/v1/project/usage')
->desc('Get project usage stats')
->groups(['api', 'usage'])
->label('scope', 'projects.read')
@@ -385,7 +385,7 @@ App::get('/v1/project/usage')
// Variables
App::post('/v1/project/variables')
Http::post('/v1/project/variables')
->desc('Create variable')
->groups(['api'])
->label('scope', 'projects.write')
@@ -448,7 +448,7 @@ App::post('/v1/project/variables')
->dynamic($variable, Response::MODEL_VARIABLE);
});
App::get('/v1/project/variables')
Http::get('/v1/project/variables')
->desc('List variables')
->groups(['api'])
->label('scope', 'projects.read')
@@ -479,7 +479,7 @@ App::get('/v1/project/variables')
]), Response::MODEL_VARIABLE_LIST);
});
App::get('/v1/project/variables/:variableId')
Http::get('/v1/project/variables/:variableId')
->desc('Get variable')
->groups(['api'])
->label('scope', 'projects.read')
@@ -509,7 +509,7 @@ App::get('/v1/project/variables/:variableId')
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
App::put('/v1/project/variables/:variableId')
Http::put('/v1/project/variables/:variableId')
->desc('Update variable')
->groups(['api'])
->label('scope', 'projects.write')
@@ -567,7 +567,7 @@ App::put('/v1/project/variables/:variableId')
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
App::delete('/v1/project/variables/:variableId')
Http::delete('/v1/project/variables/:variableId')
->desc('Delete variable')
->groups(['api'])
->label('scope', 'projects.write')
+101 -448
View File
@@ -6,7 +6,6 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Mail;
use Appwrite\Event\Validator\Event;
use Appwrite\Extend\Exception;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Email;
use Appwrite\SDK\AuthType;
@@ -15,31 +14,25 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\ProjectId;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Keys;
use Appwrite\Utopia\Response;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\Pools\Group;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
@@ -52,7 +45,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
App::init()
Http::init()
->groups(['projects'])
->inject('project')
->action(function (Document $project) {
@@ -61,252 +54,7 @@ App::init()
}
});
App::post('/v1/projects')
->desc('Create project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.create')
->label('audits.resource', 'project/{response.$id}')
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'create',
description: '/docs/references/projects/create.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_PROJECT,
)
]
))
->param('projectId', '', new ProjectId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, and hyphen. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('teamId', '', new UID(), 'Team unique ID.')
->param('region', System::getEnv('_APP_REGION', 'default'), new Whitelist(array_keys(array_filter(Config::getParam('regions'), fn ($config) => !$config['disabled']))), 'Project Region.', true)
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
->param('logo', '', new Text(1024), 'Project logo.', true)
->param('url', '', new URL(), 'Project URL.', true)
->param('legalName', '', new Text(256), 'Project legal Name. Max length: 256 chars.', true)
->param('legalCountry', '', new Text(256), 'Project legal Country. Max length: 256 chars.', true)
->param('legalState', '', new Text(256), 'Project legal State. Max length: 256 chars.', true)
->param('legalCity', '', new Text(256), 'Project legal City. Max length: 256 chars.', true)
->param('legalAddress', '', new Text(256), 'Project legal Address. Max length: 256 chars.', true)
->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true)
->inject('request')
->inject('response')
->inject('dbForPlatform')
->inject('cache')
->inject('pools')
->inject('hooks')
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForPlatform, Cache $cache, Group $pools, Hooks $hooks) {
$team = $dbForPlatform->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$allowList = \array_filter(\explode(',', System::getEnv('_APP_PROJECT_REGIONS', '')));
if (!empty($allowList) && !\in_array($region, $allowList)) {
throw new Exception(Exception::PROJECT_REGION_UNSUPPORTED, 'Region "' . $region . '" is not supported');
}
$auth = Config::getParam('auth', []);
$auths = [
'limit' => 0,
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
'passwordHistory' => 0,
'passwordDictionary' => false,
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
'personalDataCheck' => false,
'mockNumbers' => [],
'sessionAlerts' => false,
'membershipsUserName' => false,
'membershipsUserEmail' => false,
'membershipsMfa' => false,
'invalidateSessions' => true
];
foreach ($auth as $method) {
$auths[$method['key'] ?? ''] = true;
}
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
if ($projectId === 'console') {
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
$databases = Config::getParam('pools-database', []);
if ($region !== 'default') {
$databaseKeys = System::getEnv('_APP_DATABASE_KEYS', '');
$keys = explode(',', $databaseKeys);
$databases = array_filter($keys, function ($value) use ($region) {
return str_contains($value, $region);
});
}
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
$index = \array_search($databaseOverride, $databases);
if ($index !== false) {
$dsn = $databases[$index];
} else {
$dsn = $databases[array_rand($databases)];
}
// TODO: Temporary until all projects are using shared tables.
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn, $sharedTables)) {
$schema = 'appwrite';
$database = 'appwrite';
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
$dsn = $schema . '://' . $dsn . '?database=' . $database;
if (!empty($namespace)) {
$dsn .= '&namespace=' . $namespace;
}
}
try {
$project = $dbForPlatform->createDocument('projects', new Document([
'$id' => $projectId,
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'name' => $name,
'teamInternalId' => $team->getSequence(),
'teamId' => $team->getId(),
'region' => $region,
'description' => $description,
'logo' => $logo,
'url' => $url,
'version' => APP_VERSION_STABLE,
'legalName' => $legalName,
'legalCountry' => $legalCountry,
'legalState' => $legalState,
'legalCity' => $legalCity,
'legalAddress' => $legalAddress,
'legalTaxId' => ID::custom($legalTaxId),
'services' => new stdClass(),
'platforms' => null,
'oAuthProviders' => [],
'webhooks' => null,
'keys' => null,
'auths' => $auths,
'accessedAt' => DateTime::now(),
'search' => implode(' ', [$projectId, $name]),
'database' => $dsn,
'labels' => [],
]));
} catch (Duplicate) {
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
}
try {
$dsn = new DSN($dsn);
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $dsn);
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
$sharedTables = $sharedTablesV1 || $sharedTablesV2;
if (!$sharedTablesV2) {
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDatabase(APP_DATABASE);
if ($sharedTables) {
$dbForProject
->setSharedTables(true)
->setTenant($sharedTablesV1 ? (int)$project->getSequence() : null)
->setNamespace($dsn->getParam('namespace'));
} else {
$dbForProject
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getSequence());
}
$create = true;
try {
$dbForProject->create();
} catch (Duplicate) {
$create = false;
}
if ($create || $projectTables) {
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
$audit->setup();
}
if (!$create && $sharedTablesV1) {
$adapter = new AdapterDatabase($dbForProject);
$attributes = $adapter->getAttributeDocuments();
$indexes = $adapter->getIndexDocuments();
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom('audit'),
'$permissions' => [Permission::create(Role::any())],
'name' => 'audit',
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
}
if ($create || $sharedTablesV1) {
/** @var array $collections */
$collections = Config::getParam('collections', [])['projects'] ?? [];
foreach ($collections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
try {
$dbForProject->createCollection($key, $attributes, $indexes);
} catch (Duplicate) {
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom($key),
'$permissions' => [Permission::create(Role::any())],
'name' => $key,
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
}
}
}
}
// Hook allowing instant project mirroring during migration
// Outside of migration, hook is not registered and has no effect
$hooks->trigger('afterProjectCreation', [$project, $pools, $cache]);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($project, Response::MODEL_PROJECT);
});
App::get('/v1/projects/:projectId')
Http::get('/v1/projects/:projectId')
->desc('Get project')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -337,138 +85,7 @@ App::get('/v1/projects/:projectId')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId')
->desc('Update project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('audits.event', 'projects.update')
->label('audits.resource', 'project/{request.projectId}')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'update',
description: '/docs/references/projects/update.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROJECT,
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
->param('logo', '', new Text(1024), 'Project logo.', true)
->param('url', '', new URL(), 'Project URL.', true)
->param('legalName', '', new Text(256), 'Project legal name. Max length: 256 chars.', true)
->param('legalCountry', '', new Text(256), 'Project legal country. Max length: 256 chars.', true)
->param('legalState', '', new Text(256), 'Project legal state. Max length: 256 chars.', true)
->param('legalCity', '', new Text(256), 'Project legal city. Max length: 256 chars.', true)
->param('legalAddress', '', new Text(256), 'Project legal address. Max length: 256 chars.', true)
->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true)
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform) {
$project = $dbForPlatform->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project
->setAttribute('name', $name)
->setAttribute('description', $description)
->setAttribute('logo', $logo)
->setAttribute('url', $url)
->setAttribute('legalName', $legalName)
->setAttribute('legalCountry', $legalCountry)
->setAttribute('legalState', $legalState)
->setAttribute('legalCity', $legalCity)
->setAttribute('legalAddress', $legalAddress)
->setAttribute('legalTaxId', $legalTaxId)
->setAttribute('search', implode(' ', [$projectId, $name])));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/team')
->desc('Update project team')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk', new Method(
namespace: 'projects',
group: 'projects',
name: 'updateTeam',
description: '/docs/references/projects/update-team.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PROJECT,
)
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('teamId', '', new UID(), 'Team ID of the team to transfer project to.')
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $teamId, Response $response, Database $dbForPlatform) {
$project = $dbForPlatform->getDocument('projects', $projectId);
$team = $dbForPlatform->getDocument('teams', $teamId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$permissions = [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
];
$project
->setAttribute('teamId', $teamId)
->setAttribute('teamInternalId', $team->getSequence())
->setAttribute('$permissions', $permissions);
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project);
$installations = $dbForPlatform->find('installations', [
Query::equal('projectInternalId', [$project->getSequence()]),
]);
foreach ($installations as $installation) {
$installation->getAttribute('$permissions', $permissions);
$dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
}
$repositories = $dbForPlatform->find('repositories', [
Query::equal('projectInternalId', [$project->getSequence()]),
]);
foreach ($repositories as $repository) {
$repository->getAttribute('$permissions', $permissions);
$dbForPlatform->updateDocument('repositories', $repository->getId(), $repository);
}
$vcsComments = $dbForPlatform->find('vcsComments', [
Query::equal('projectInternalId', [$project->getSequence()]),
]);
foreach ($vcsComments as $vcsComment) {
$vcsComment->getAttribute('$permissions', $permissions);
$dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), $vcsComment);
}
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/service')
Http::patch('/v1/projects/:projectId/service')
->desc('Update service status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -506,7 +123,7 @@ App::patch('/v1/projects/:projectId/service')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/service/all')
Http::patch('/v1/projects/:projectId/service/all')
->desc('Update all service status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -547,7 +164,7 @@ App::patch('/v1/projects/:projectId/service/all')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/api')
Http::patch('/v1/projects/:projectId/api')
->desc('Update API status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -605,7 +222,7 @@ App::patch('/v1/projects/:projectId/api')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/api/all')
Http::patch('/v1/projects/:projectId/api/all')
->desc('Update all API status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -666,7 +283,7 @@ App::patch('/v1/projects/:projectId/api/all')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/oauth2')
Http::patch('/v1/projects/:projectId/oauth2')
->desc('Update project OAuth2')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -717,7 +334,7 @@ App::patch('/v1/projects/:projectId/oauth2')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/session-alerts')
Http::patch('/v1/projects/:projectId/auth/session-alerts')
->desc('Update project sessions emails')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -755,7 +372,7 @@ App::patch('/v1/projects/:projectId/auth/session-alerts')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/memberships-privacy')
Http::patch('/v1/projects/:projectId/auth/memberships-privacy')
->desc('Update project memberships privacy attributes')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -797,7 +414,7 @@ App::patch('/v1/projects/:projectId/auth/memberships-privacy')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/limit')
Http::patch('/v1/projects/:projectId/auth/limit')
->desc('Update project users limit')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -835,7 +452,7 @@ App::patch('/v1/projects/:projectId/auth/limit')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/duration')
Http::patch('/v1/projects/:projectId/auth/duration')
->desc('Update project authentication duration')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -873,7 +490,7 @@ App::patch('/v1/projects/:projectId/auth/duration')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/:method')
Http::patch('/v1/projects/:projectId/auth/:method')
->desc('Update project auth method status. Use this endpoint to enable or disable a given auth method for this project.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -914,7 +531,7 @@ App::patch('/v1/projects/:projectId/auth/:method')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/password-history')
Http::patch('/v1/projects/:projectId/auth/password-history')
->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -952,7 +569,7 @@ App::patch('/v1/projects/:projectId/auth/password-history')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/password-dictionary')
Http::patch('/v1/projects/:projectId/auth/password-dictionary')
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -990,7 +607,7 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/personal-data')
Http::patch('/v1/projects/:projectId/auth/personal-data')
->desc('Update personal data check')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1028,7 +645,7 @@ App::patch('/v1/projects/:projectId/auth/personal-data')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/max-sessions')
Http::patch('/v1/projects/:projectId/auth/max-sessions')
->desc('Update project user sessions limit')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1066,7 +683,7 @@ App::patch('/v1/projects/:projectId/auth/max-sessions')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/mock-numbers')
Http::patch('/v1/projects/:projectId/auth/mock-numbers')
->desc('Update the mock numbers for the project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1112,7 +729,7 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::delete('/v1/projects/:projectId')
Http::delete('/v1/projects/:projectId')
->desc('Delete project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.delete')
@@ -1158,7 +775,7 @@ App::delete('/v1/projects/:projectId')
// Webhooks
App::post('/v1/projects/:projectId/webhooks')
Http::post('/v1/projects/:projectId/webhooks')
->desc('Create webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1223,7 +840,7 @@ App::post('/v1/projects/:projectId/webhooks')
->dynamic($webhook, Response::MODEL_WEBHOOK);
});
App::get('/v1/projects/:projectId/webhooks')
Http::get('/v1/projects/:projectId/webhooks')
->desc('List webhooks')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -1263,7 +880,7 @@ App::get('/v1/projects/:projectId/webhooks')
]), Response::MODEL_WEBHOOK_LIST);
});
App::get('/v1/projects/:projectId/webhooks/:webhookId')
Http::get('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Get webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -1304,7 +921,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId')
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
App::put('/v1/projects/:projectId/webhooks/:webhookId')
Http::put('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Update webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1370,7 +987,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
->desc('Update webhook signature key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1416,7 +1033,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
App::delete('/v1/projects/:projectId/webhooks/:webhookId')
Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Delete webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1464,7 +1081,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
// Keys
App::post('/v1/projects/:projectId/keys')
Http::post('/v1/projects/:projectId/keys')
->desc('Create key')
->groups(['api', 'projects'])
->label('scope', 'keys.write')
@@ -1482,12 +1099,15 @@ App::post('/v1/projects/:projectId/keys')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
// TODO: When migrating to Platform API, mark keyId required for consistency
->param('keyId', 'unique()', new CustomId(), 'Key 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.', true)
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
$keyId = $keyId == 'unique()' ? ID::unique() : $keyId;
$project = $dbForPlatform->getDocument('projects', $projectId);
@@ -1496,15 +1116,12 @@ App::post('/v1/projects/:projectId/keys')
}
$key = new Document([
'$id' => ID::unique(),
'$id' => $keyId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
// TODO: @hmacr Remove `projectInternalId` and `projectId` column writes before deleting the column.
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
@@ -1516,7 +1133,11 @@ App::post('/v1/projects/:projectId/keys')
'secret' => API_KEY_STANDARD . '_' . \bin2hex(\random_bytes(128)),
]);
$key = $dbForPlatform->createDocument('keys', $key);
try {
$key = $dbForPlatform->createDocument('keys', $key);
} catch (Duplicate) {
throw new Exception(Exception::KEY_ALREADY_EXISTS);
}
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
@@ -1525,7 +1146,7 @@ App::post('/v1/projects/:projectId/keys')
->dynamic($key, Response::MODEL_KEY);
});
App::get('/v1/projects/:projectId/keys')
Http::get('/v1/projects/:projectId/keys')
->desc('List keys')
->groups(['api', 'projects'])
->label('scope', 'keys.read')
@@ -1543,10 +1164,11 @@ App::get('/v1/projects/:projectId/keys')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('queries', [], new Keys(), '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(', ', Keys::ALLOWED_ATTRIBUTES), true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForPlatform')
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
->action(function (string $projectId, array $queries, bool $includeTotal, Response $response, Database $dbForPlatform) {
$project = $dbForPlatform->getDocument('projects', $projectId);
@@ -1554,19 +1176,50 @@ App::get('/v1/projects/:projectId/keys')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$keys = $dbForPlatform->find('keys', [
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
Query::limit(5000),
]);
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
// Backwards compatibility
if (\count(Query::getByType($queries, [Query::TYPE_LIMIT])) === 0) {
$queries[] = Query::limit(5000);
}
$queries[] = Query::equal('resourceType', ['projects']);
$queries[] = Query::equal('resourceInternalId', [$project->getSequence()]);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$keyId = $cursor->getValue();
$cursorDocument = $dbForPlatform->getDocument('keys', $keyId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Key '{$keyId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$keys = $dbForPlatform->find('keys', $queries);
$response->dynamic(new Document([
'keys' => $keys,
'total' => $includeTotal ? count($keys) : 0,
'total' => $includeTotal ? $dbForPlatform->count('keys', $filterQueries, APP_LIMIT_COUNT) : 0,
]), Response::MODEL_KEY_LIST);
});
App::get('/v1/projects/:projectId/keys/:keyId')
Http::get('/v1/projects/:projectId/keys/:keyId')
->desc('Get key')
->groups(['api', 'projects'])
->label('scope', 'keys.read')
@@ -1608,7 +1261,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
$response->dynamic($key, Response::MODEL_KEY);
});
App::put('/v1/projects/:projectId/keys/:keyId')
Http::put('/v1/projects/:projectId/keys/:keyId')
->desc('Update key')
->groups(['api', 'projects'])
->label('scope', 'keys.write')
@@ -1628,7 +1281,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForPlatform')
@@ -1662,7 +1315,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
$response->dynamic($key, Response::MODEL_KEY);
});
App::delete('/v1/projects/:projectId/keys/:keyId')
Http::delete('/v1/projects/:projectId/keys/:keyId')
->desc('Delete key')
->groups(['api', 'projects'])
->label('scope', 'keys.write')
@@ -1711,7 +1364,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
// JWT Keys
App::post('/v1/projects/:projectId/jwts')
Http::post('/v1/projects/:projectId/jwts')
->groups(['api', 'projects'])
->desc('Create JWT')
->label('scope', 'projects.write')
@@ -1729,7 +1382,7 @@ App::post('/v1/projects/:projectId/jwts')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForPlatform')
@@ -1753,7 +1406,7 @@ App::post('/v1/projects/:projectId/jwts')
// Platforms
App::post('/v1/projects/:projectId/platforms')
Http::post('/v1/projects/:projectId/platforms')
->desc('Create platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.create')
@@ -1833,7 +1486,7 @@ App::post('/v1/projects/:projectId/platforms')
->dynamic($platform, Response::MODEL_PLATFORM);
});
App::get('/v1/projects/:projectId/platforms')
Http::get('/v1/projects/:projectId/platforms')
->desc('List platforms')
->groups(['api', 'projects'])
->label('scope', 'platforms.read')
@@ -1873,7 +1526,7 @@ App::get('/v1/projects/:projectId/platforms')
]), Response::MODEL_PLATFORM_LIST);
});
App::get('/v1/projects/:projectId/platforms/:platformId')
Http::get('/v1/projects/:projectId/platforms/:platformId')
->desc('Get platform')
->groups(['api', 'projects'])
->label('scope', 'platforms.read')
@@ -1914,7 +1567,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId')
$response->dynamic($platform, Response::MODEL_PLATFORM);
});
App::put('/v1/projects/:projectId/platforms/:platformId')
Http::put('/v1/projects/:projectId/platforms/:platformId')
->desc('Update platform')
->groups(['api', 'projects'])
->label('scope', 'platforms.write')
@@ -1968,7 +1621,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId')
$response->dynamic($platform, Response::MODEL_PLATFORM);
});
App::delete('/v1/projects/:projectId/platforms/:platformId')
Http::delete('/v1/projects/:projectId/platforms/:platformId')
->desc('Delete platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.delete')
@@ -2018,7 +1671,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId')
// CUSTOM SMTP and Templates
App::patch('/v1/projects/:projectId/smtp')
Http::patch('/v1/projects/:projectId/smtp')
->desc('Update SMTP')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2136,7 +1789,7 @@ App::patch('/v1/projects/:projectId/smtp')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::post('/v1/projects/:projectId/smtp/tests')
Http::post('/v1/projects/:projectId/smtp/tests')
->desc('Create SMTP test')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2231,7 +1884,7 @@ App::post('/v1/projects/:projectId/smtp/tests')
return $response->noContent();
});
App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
->desc('Get custom SMS template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2299,7 +1952,7 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
});
App::get('/v1/projects/:projectId/templates/email/:type/:locale')
Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
->desc('Get custom email template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2398,7 +2051,7 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale')
$response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE);
});
App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
->desc('Update custom SMS template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2465,7 +2118,7 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
]), Response::MODEL_SMS_TEMPLATE);
});
App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
->desc('Update custom email templates')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2522,7 +2175,7 @@ App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
]), Response::MODEL_EMAIL_TEMPLATE);
});
App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
->desc('Reset custom SMS template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2594,7 +2247,7 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
]), Response::MODEL_SMS_TEMPLATE);
});
App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
->desc('Delete custom email template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2647,7 +2300,7 @@ App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
]), Response::MODEL_EMAIL_TEMPLATE);
});
App::patch('/v1/projects/:projectId/auth/session-invalidation')
Http::patch('/v1/projects/:projectId/auth/session-invalidation')
->desc('Update invalidate session option of the project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
+32 -57
View File
@@ -26,7 +26,6 @@ use Appwrite\Utopia\Response;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
@@ -52,15 +51,15 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::post('/v1/teams')
Http::post('/v1/teams')
->desc('Create team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].create')
@@ -156,7 +155,7 @@ App::post('/v1/teams')
->dynamic($team, Response::MODEL_TEAM);
});
App::get('/v1/teams')
Http::get('/v1/teams')
->desc('List teams')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -191,16 +190,10 @@ App::get('/v1/teams')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -230,7 +223,7 @@ App::get('/v1/teams')
]), Response::MODEL_TEAM_LIST);
});
App::get('/v1/teams/:teamId')
Http::get('/v1/teams/:teamId')
->desc('Get team')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -261,7 +254,7 @@ App::get('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
App::get('/v1/teams/:teamId/prefs')
Http::get('/v1/teams/:teamId/prefs')
->desc('Get team preferences')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -300,7 +293,7 @@ App::get('/v1/teams/:teamId/prefs')
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
});
App::put('/v1/teams/:teamId')
Http::put('/v1/teams/:teamId')
->desc('Update name')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update')
@@ -345,7 +338,7 @@ App::put('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
App::put('/v1/teams/:teamId/prefs')
Http::put('/v1/teams/:teamId/prefs')
->desc('Update preferences')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update.prefs')
@@ -393,7 +386,7 @@ App::put('/v1/teams/:teamId/prefs')
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
});
App::delete('/v1/teams/:teamId')
Http::delete('/v1/teams/:teamId')
->desc('Delete team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].delete')
@@ -433,15 +426,22 @@ App::delete('/v1/teams/:teamId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
}
// Sync delete
$deletes = new Deletes();
$deletes->deleteMemberships($getProjectDB, $team, $project);
// Async delete
if ($project->getId() === 'console') {
$queueForDeletes
->setType(DELETE_TYPE_TEAM_PROJECTS)
->setDocument($team);
->setDocument($team)
->trigger();
}
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($team);
$queueForEvents
->setParam('teamId', $team->getId())
->setPayload($response->output($team, Response::MODEL_TEAM))
@@ -450,7 +450,7 @@ App::delete('/v1/teams/:teamId')
$response->noContent();
});
App::post('/v1/teams/:teamId/memberships')
Http::post('/v1/teams/:teamId/memberships')
->desc('Create team membership')
->groups(['api', 'teams', 'auth'])
->label('event', 'teams.[teamId].memberships.[membershipId].create')
@@ -477,16 +477,7 @@ App::post('/v1/teams/:teamId/memberships')
->param('email', '', new EmailValidator(), 'Email of the new team member.', true)
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], function (Document $project) {
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
$roles = array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'Array of strings. Use this param to set the user roles in the team. 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.', false, ['project'])
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. 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.', false, ['project'])
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
@@ -772,7 +763,7 @@ App::post('/v1/teams/:teamId/memberships')
->setPreview($preview)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name', ''))
->setVariables($emailVariables)
->appendVariables($emailVariables)
->trigger();
} elseif (!empty($phone)) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
@@ -837,7 +828,7 @@ App::post('/v1/teams/:teamId/memberships')
);
});
App::get('/v1/teams/:teamId/memberships')
Http::get('/v1/teams/:teamId/memberships')
->desc('List team memberships')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -882,22 +873,15 @@ App::get('/v1/teams/:teamId/memberships')
// Set internal queries
$queries[] = Query::equal('teamInternalId', [$team->getSequence()]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$membershipId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('memberships', $membershipId);
@@ -981,7 +965,7 @@ App::get('/v1/teams/:teamId/memberships')
]), Response::MODEL_MEMBERSHIP_LIST);
});
App::get('/v1/teams/:teamId/memberships/:membershipId')
Http::get('/v1/teams/:teamId/memberships/:membershipId')
->desc('Get team membership')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -1066,7 +1050,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
});
App::patch('/v1/teams/:teamId/memberships/:membershipId')
Http::patch('/v1/teams/:teamId/memberships/:membershipId')
->desc('Update membership')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].update')
@@ -1088,16 +1072,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('roles', [], function (Document $project) {
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
$roles = array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'An array of strings. Use this param to set the user\'s roles in the team. 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.', false, ['project'])
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. 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.', false, ['project'])
->inject('request')
->inject('response')
->inject('user')
@@ -1176,7 +1151,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
);
});
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->desc('Update team membership status')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].update.status')
@@ -1343,7 +1318,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
);
});
App::delete('/v1/teams/:teamId/memberships/:membershipId')
Http::delete('/v1/teams/:teamId/memberships/:membershipId')
->desc('Delete team membership')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].delete')
@@ -1443,7 +1418,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$response->noContent();
});
App::get('/v1/teams/:teamId/logs')
Http::get('/v1/teams/:teamId/logs')
->desc('List team logs')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
+57 -70
View File
@@ -29,7 +29,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Hash;
use Utopia\Auth\Hashes\Argon2;
@@ -61,6 +60,7 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -227,7 +227,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
return $user;
}
App::post('/v1/users')
Http::post('/v1/users')
->desc('Create user')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -264,7 +264,7 @@ App::post('/v1/users')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/bcrypt')
Http::post('/v1/users/bcrypt')
->desc('Create user with bcrypt password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -302,7 +302,7 @@ App::post('/v1/users/bcrypt')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/md5')
Http::post('/v1/users/md5')
->desc('Create user with MD5 password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -339,7 +339,7 @@ App::post('/v1/users/md5')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/argon2')
Http::post('/v1/users/argon2')
->desc('Create user with Argon2 password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -376,7 +376,7 @@ App::post('/v1/users/argon2')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/sha')
Http::post('/v1/users/sha')
->desc('Create user with SHA password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -417,7 +417,7 @@ App::post('/v1/users/sha')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/phpass')
Http::post('/v1/users/phpass')
->desc('Create user with PHPass password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -454,7 +454,7 @@ App::post('/v1/users/phpass')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/scrypt')
Http::post('/v1/users/scrypt')
->desc('Create user with Scrypt password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -502,7 +502,7 @@ App::post('/v1/users/scrypt')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/scrypt-modified')
Http::post('/v1/users/scrypt-modified')
->desc('Create user with Scrypt modified password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -546,7 +546,7 @@ App::post('/v1/users/scrypt-modified')
->dynamic($user, Response::MODEL_USER);
});
App::post('/v1/users/:userId/targets')
Http::post('/v1/users/:userId/targets')
->desc('Create user target')
->groups(['api', 'users'])
->label('audits.event', 'target.create')
@@ -641,7 +641,7 @@ App::post('/v1/users/:userId/targets')
->dynamic($target, Response::MODEL_TARGET);
});
App::get('/v1/users')
Http::get('/v1/users')
->desc('List users')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -675,16 +675,10 @@ App::get('/v1/users')
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -720,7 +714,7 @@ App::get('/v1/users')
]), Response::MODEL_USER_LIST);
});
App::get('/v1/users/:userId')
Http::get('/v1/users/:userId')
->desc('Get user')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -751,7 +745,7 @@ App::get('/v1/users/:userId')
$response->dynamic($user, Response::MODEL_USER);
});
App::get('/v1/users/:userId/prefs')
Http::get('/v1/users/:userId/prefs')
->desc('Get user preferences')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -784,7 +778,7 @@ App::get('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
App::get('/v1/users/:userId/targets/:targetId')
Http::get('/v1/users/:userId/targets/:targetId')
->desc('Get user target')
->groups(['api', 'users'])
->label('scope', 'targets.read')
@@ -822,7 +816,7 @@ App::get('/v1/users/:userId/targets/:targetId')
$response->dynamic($target, Response::MODEL_TARGET);
});
App::get('/v1/users/:userId/sessions')
Http::get('/v1/users/:userId/sessions')
->desc('List user sessions')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -866,7 +860,7 @@ App::get('/v1/users/:userId/sessions')
]), Response::MODEL_SESSION_LIST);
});
App::get('/v1/users/:userId/memberships')
Http::get('/v1/users/:userId/memberships')
->desc('List user memberships')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -921,7 +915,7 @@ App::get('/v1/users/:userId/memberships')
]), Response::MODEL_MEMBERSHIP_LIST);
});
App::get('/v1/users/:userId/logs')
Http::get('/v1/users/:userId/logs')
->desc('List user logs')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -1008,7 +1002,7 @@ App::get('/v1/users/:userId/logs')
]), Response::MODEL_LOG_LIST);
});
App::get('/v1/users/:userId/targets')
Http::get('/v1/users/:userId/targets')
->desc('List user targets')
->groups(['api', 'users'])
->label('scope', 'targets.read')
@@ -1042,14 +1036,11 @@ App::get('/v1/users/:userId/targets')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries[] = Query::equal('userId', [$userId]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -1073,7 +1064,7 @@ App::get('/v1/users/:userId/targets')
]), Response::MODEL_TARGET_LIST);
});
App::get('/v1/users/identities')
Http::get('/v1/users/identities')
->desc('List identities')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -1106,15 +1097,11 @@ App::get('/v1/users/identities')
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -1139,7 +1126,7 @@ App::get('/v1/users/identities')
]), Response::MODEL_IDENTITY_LIST);
});
App::patch('/v1/users/:userId/status')
Http::patch('/v1/users/:userId/status')
->desc('Update user status')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.status')
@@ -1181,7 +1168,7 @@ App::patch('/v1/users/:userId/status')
$response->dynamic($user, Response::MODEL_USER);
});
App::put('/v1/users/:userId/labels')
Http::put('/v1/users/:userId/labels')
->desc('Update user labels')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.labels')
@@ -1224,7 +1211,7 @@ App::put('/v1/users/:userId/labels')
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/verification/phone')
Http::patch('/v1/users/:userId/verification/phone')
->desc('Update phone verification')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.verification')
@@ -1265,7 +1252,7 @@ App::patch('/v1/users/:userId/verification/phone')
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/name')
Http::patch('/v1/users/:userId/name')
->desc('Update name')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.name')
@@ -1308,7 +1295,7 @@ App::patch('/v1/users/:userId/name')
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/password')
Http::patch('/v1/users/:userId/password')
->desc('Update password')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.password')
@@ -1407,7 +1394,7 @@ App::patch('/v1/users/:userId/password')
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/email')
Http::patch('/v1/users/:userId/email')
->desc('Update email')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.email')
@@ -1519,7 +1506,7 @@ App::patch('/v1/users/:userId/email')
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/phone')
Http::patch('/v1/users/:userId/phone')
->desc('Update phone')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.phone')
@@ -1608,7 +1595,7 @@ App::patch('/v1/users/:userId/phone')
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/verification')
Http::patch('/v1/users/:userId/verification')
->desc('Update email verification')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.verification')
@@ -1649,7 +1636,7 @@ App::patch('/v1/users/:userId/verification')
$response->dynamic($user, Response::MODEL_USER);
});
App::patch('/v1/users/:userId/prefs')
Http::patch('/v1/users/:userId/prefs')
->desc('Update user preferences')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.prefs')
@@ -1688,7 +1675,7 @@ App::patch('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
App::patch('/v1/users/:userId/targets/:targetId')
Http::patch('/v1/users/:userId/targets/:targetId')
->desc('Update user target')
->groups(['api', 'users'])
->label('audits.event', 'target.update')
@@ -1791,7 +1778,7 @@ App::patch('/v1/users/:userId/targets/:targetId')
->dynamic($target, Response::MODEL_TARGET);
});
App::patch('/v1/users/:userId/mfa')
Http::patch('/v1/users/:userId/mfa')
->desc('Update MFA')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa')
@@ -1855,7 +1842,7 @@ App::patch('/v1/users/:userId/mfa')
$response->dynamic($user, Response::MODEL_USER);
});
App::get('/v1/users/:userId/mfa/factors')
Http::get('/v1/users/:userId/mfa/factors')
->desc('List factors')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -1914,7 +1901,7 @@ App::get('/v1/users/:userId/mfa/factors')
$response->dynamic($factors, Response::MODEL_MFA_FACTORS);
});
App::get('/v1/users/:userId/mfa/recovery-codes')
Http::get('/v1/users/:userId/mfa/recovery-codes')
->desc('Get MFA recovery codes')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -1975,7 +1962,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
App::patch('/v1/users/:userId/mfa/recovery-codes')
Http::patch('/v1/users/:userId/mfa/recovery-codes')
->desc('Create MFA recovery codes')
->groups(['api', 'users'])
->label('event', 'users.[userId].create.mfa.recovery-codes')
@@ -2047,7 +2034,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
App::put('/v1/users/:userId/mfa/recovery-codes')
Http::put('/v1/users/:userId/mfa/recovery-codes')
->desc('Update MFA recovery codes (regenerate)')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa.recovery-codes')
@@ -2119,7 +2106,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
App::delete('/v1/users/:userId/mfa/authenticators/:type')
Http::delete('/v1/users/:userId/mfa/authenticators/:type')
->desc('Delete authenticator')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete.mfa')
@@ -2189,7 +2176,7 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type')
$response->noContent();
});
App::post('/v1/users/:userId/sessions')
Http::post('/v1/users/:userId/sessions')
->desc('Create session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -2282,7 +2269,7 @@ App::post('/v1/users/:userId/sessions')
->dynamic($session, Response::MODEL_SESSION);
});
App::post('/v1/users/:userId/tokens')
Http::post('/v1/users/:userId/tokens')
->desc('Create token')
->groups(['api', 'users'])
->label('event', 'users.[userId].tokens.[tokenId].create')
@@ -2347,7 +2334,7 @@ App::post('/v1/users/:userId/tokens')
->dynamic($token, Response::MODEL_TOKEN);
});
App::delete('/v1/users/:userId/sessions/:sessionId')
Http::delete('/v1/users/:userId/sessions/:sessionId')
->desc('Delete user session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].delete')
@@ -2398,7 +2385,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
$response->noContent();
});
App::delete('/v1/users/:userId/sessions')
Http::delete('/v1/users/:userId/sessions')
->desc('Delete user sessions')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.delete')
@@ -2448,7 +2435,7 @@ App::delete('/v1/users/:userId/sessions')
$response->noContent();
});
App::delete('/v1/users/:userId')
Http::delete('/v1/users/:userId')
->desc('Delete user')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete')
@@ -2500,7 +2487,7 @@ App::delete('/v1/users/:userId')
$response->noContent();
});
App::delete('/v1/users/:userId/targets/:targetId')
Http::delete('/v1/users/:userId/targets/:targetId')
->desc('Delete user target')
->groups(['api', 'users'])
->label('audits.event', 'target.delete')
@@ -2558,7 +2545,7 @@ App::delete('/v1/users/:userId/targets/:targetId')
$response->noContent();
});
App::delete('/v1/users/identities/:identityId')
Http::delete('/v1/users/identities/:identityId')
->desc('Delete identity')
->groups(['api', 'users'])
->label('event', 'users.[userId].identities.[identityId].delete')
@@ -2601,7 +2588,7 @@ App::delete('/v1/users/identities/:identityId')
return $response->noContent();
});
App::post('/v1/users/:userId/jwts')
Http::post('/v1/users/:userId/jwts')
->desc('Create user JWT')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -2658,7 +2645,7 @@ App::post('/v1/users/:userId/jwts')
])]), Response::MODEL_JWT);
});
App::get('/v1/users/usage')
Http::get('/v1/users/usage')
->desc('Get users usage stats')
->groups(['api', 'users'])
->label('scope', 'users.read')
File diff suppressed because it is too large Load Diff
+85 -61
View File
@@ -8,7 +8,7 @@ use Appwrite\Auth\Key;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Execution;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Network\Cors;
@@ -34,7 +34,6 @@ use Appwrite\Utopia\View;
use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Http\Request as SwooleRequest;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -46,6 +45,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Domains\Domain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
@@ -60,7 +60,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
{
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
@@ -85,22 +85,32 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$platformHostnames = $platform['hostnames'] ?? [];
if ($rule->isEmpty()) {
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$appDomainFunctions = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$appDomainSites = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($appDomainFunctionsFallback) && \str_ends_with($host, $appDomainFunctionsFallback)) {
$appDomainFunctions = $appDomainFunctionsFallback;
$denyDomains = [];
$denyEnvVars = [
System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''),
System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
System::getEnv('_APP_DOMAIN_SITES', ''),
];
foreach ($denyEnvVars as $denyEnvVar) {
foreach (\explode(',', $denyEnvVar) as $denyDomain) {
if (empty($denyDomain)) {
continue;
}
$denyDomains[] = $denyDomain;
}
}
if ($host === $appDomainFunctions || $host === $appDomainSites) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.', view: $errorView);
}
foreach ($denyDomains as $denyDomain) {
if ($host === $denyDomain) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.', view: $errorView);
}
if (\str_ends_with($host, $appDomainFunctions) || \str_ends_with($host, $appDomainSites)) {
$exception = new AppwriteException(AppwriteException::RULE_NOT_FOUND, 'This domain is not connected to any Appwrite resources. Visit domains tab under function/site settings to configure it.', view: $errorView);
if (\str_ends_with($host, $denyDomain)) {
$exception = new AppwriteException(AppwriteException::RULE_NOT_FOUND, 'This domain is not connected to any Appwrite resources. Visit domains tab under function/site settings to configure it.', view: $errorView);
$exception->addCTA('Start with this domain', $url . '/console');
throw $exception;
$exception->addCTA('Start with this domain', $url . '/console');
throw $exception;
}
}
if (!in_array($host, $platformHostnames)) {
@@ -686,14 +696,11 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
throw $th;
}
} finally {
if ($type === 'function') {
$queueForFunctions
->setType(Func::TYPE_ASYNC_WRITE)
if ($type === 'function' || $type === 'site') {
$queueForExecutions
->setExecution($execution)
->setProject($project)
->trigger();
} elseif ($type === 'site') { // TODO: Move it to logs worker later
$dbForProject->createDocument('executions', $execution);
}
}
@@ -829,7 +836,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
}
App::init()
Http::init()
->groups(['api'])
->inject('project')
->inject('mode')
@@ -839,7 +846,7 @@ App::init()
}
});
App::init()
Http::init()
->groups(['database', 'functions', 'sites', 'messaging'])
->inject('project')
->inject('request')
@@ -852,7 +859,7 @@ App::init()
}
});
App::init()
Http::init()
->groups(['api', 'web'])
->inject('utopia')
->inject('swooleRequest')
@@ -867,7 +874,7 @@ App::init()
->inject('geodb')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('platform')
->inject('isResourceBlocked')
@@ -878,7 +885,7 @@ App::init()
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Execution $queueForExecutions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
/*
* Appwrite Router
*/
@@ -886,7 +893,7 @@ App::init()
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -940,7 +947,15 @@ App::init()
$endDomain->getRegisterable() !== ''
);
$isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort();
$localHosts = ['localhost','localhost:'.$request->getPort()];
$migrationHost = System::getEnv('_APP_MIGRATION_HOST');
if (!empty($migrationHost)) {
$localHosts[] = $migrationHost;
$localHosts[] = $migrationHost.':'.$request->getPort();
}
$isLocalHost = in_array($request->getHostname(), $localHosts);
$isIpAddress = filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false;
$isConsoleProject = $project->getAttribute('$id', '') === 'console';
@@ -987,7 +1002,7 @@ App::init()
}
if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost') { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
if ($request->getProtocol() !== 'https' && !in_array(($swooleRequest->header['host'] ?? ''), $localHosts)) { // localhost allowed for proxy
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
@@ -1002,7 +1017,7 @@ App::init()
*
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
App::init()
Http::init()
->groups(['api', 'web'])
->inject('request')
->inject('response')
@@ -1042,7 +1057,7 @@ App::init()
/**
* Automatic certificate generation
*/
App::init()
Http::init()
->groups(['api', 'web'])
->inject('request')
->inject('console')
@@ -1094,19 +1109,28 @@ App::init()
// 5. Create new rule
$owner = '';
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
$funcDomain = $fallback;
// Mark owner as Appwrite if its appwrite-owned domain
$appwriteDomains = [];
$appwriteDomainEnvs = [
System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''),
System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
System::getEnv('_APP_DOMAIN_SITES', ''),
];
foreach ($appwriteDomainEnvs as $appwriteDomainEnv) {
foreach (\explode(',', $appwriteDomainEnv) as $appwriteDomain) {
if (empty($appwriteDomain)) {
continue;
}
$appwriteDomains[] = $appwriteDomain;
}
}
if (
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
) {
$owner = 'Appwrite';
foreach ($appwriteDomains as $appwriteDomain) {
if (\str_ends_with($domain->get(), $appwriteDomain)) {
$owner = 'Appwrite';
break;
}
}
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
@@ -1138,7 +1162,7 @@ App::init()
});
});
App::options()
Http::options()
->inject('utopia')
->inject('swooleRequest')
->inject('request')
@@ -1148,7 +1172,7 @@ App::options()
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
@@ -1161,14 +1185,14 @@ App::options()
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
/*
* Appwrite Router
*/
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1192,7 +1216,7 @@ App::options()
->trigger();
});
App::error()
Http::error()
->inject('error')
->inject('utopia')
->inject('request')
@@ -1203,7 +1227,7 @@ App::error()
->inject('queueForStatsUsage')
->inject('devKey')
->inject('authorization')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
->action(function (Throwable $error, Http $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute();
$class = \get_class($error);
@@ -1479,7 +1503,7 @@ App::error()
$type = $error->getType();
$output = App::isDevelopment() ? [
$output = Http::isDevelopment() ? [
'message' => $message,
'code' => $code,
'file' => $file,
@@ -1503,7 +1527,7 @@ App::error()
$template = $error->getView() ?? (($route) ? $route->getLabel('error', null) : null);
// TODO: Ideally use group 'api' here, but all wildcard routes seem to have 'api' at the moment
if (!\str_starts_with($route->getPath(), '/v1')) {
if (empty($route) || !\str_starts_with($route->getPath(), '/v1')) {
$template = __DIR__ . '/../views/general/error.phtml';
}
@@ -1512,7 +1536,7 @@ App::error()
$layout
->setParam('title', $project->getAttribute('name') . ' - Error')
->setParam('development', App::isDevelopment())
->setParam('development', Http::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $output['message'] ?? '')
@@ -1531,7 +1555,7 @@ App::error()
);
});
App::get('/robots.txt')
Http::get('/robots.txt')
->desc('Robots.txt File')
->label('scope', 'public')
->label('docs', false)
@@ -1544,7 +1568,7 @@ App::get('/robots.txt')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
@@ -1554,19 +1578,19 @@ App::get('/robots.txt')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
});
App::get('/humans.txt')
Http::get('/humans.txt')
->desc('Humans.txt File')
->label('scope', 'public')
->label('docs', false)
@@ -1579,7 +1603,7 @@ App::get('/humans.txt')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForExecutions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
@@ -1589,19 +1613,19 @@ App::get('/humans.txt')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
$utopia->getRoute()?->label('router', true);
}
}
});
App::get('/.well-known/acme-challenge/*')
Http::get('/.well-known/acme-challenge/*')
->desc('SSL Verification')
->label('scope', 'public')
->label('docs', false)
@@ -1654,7 +1678,7 @@ App::get('/.well-known/acme-challenge/*')
include_once __DIR__ . '/shared/api.php';
include_once __DIR__ . '/shared/api/auth.php';
App::get('/v1/ping')
Http::get('/v1/ping')
->groups(['api', 'general'])
->desc('Test the connection between the Appwrite and the SDK.')
->label('scope', 'global')
@@ -1704,7 +1728,7 @@ App::get('/v1/ping')
});
// Preview authorization
App::get('/_appwrite/authorize')
Http::get('/_appwrite/authorize')
->inject('request')
->inject('response')
->inject('previewHostname')
@@ -1731,7 +1755,7 @@ App::get('/_appwrite/authorize')
->redirect($protocol . '://' . $host . $path);
});
App::wildcard()
Http::wildcard()
->groups(['api'])
->label('scope', 'global')
->action(function () {
+13 -16
View File
@@ -5,7 +5,6 @@ global $utopia, $request, $response;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -13,13 +12,14 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
App::get('/v1/mock/tests/general/oauth2')
Http::get('/v1/mock/tests/general/oauth2')
->desc('OAuth Login')
->groups(['mock'])
->label('scope', 'public')
@@ -35,7 +35,7 @@ App::get('/v1/mock/tests/general/oauth2')
$response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
});
App::get('/v1/mock/tests/locale')
Http::get('/v1/mock/tests/locale')
->desc('Mock locale translation key')
->groups(['mock'])
->label('scope', 'public')
@@ -54,7 +54,7 @@ App::get('/v1/mock/tests/locale')
$response->send($locale->getText('mock'));
});
App::get('/v1/mock/tests/general/oauth2/token')
Http::get('/v1/mock/tests/general/oauth2/token')
->desc('OAuth2 Token')
->groups(['mock'])
->label('scope', 'public')
@@ -100,7 +100,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
}
});
App::get('/v1/mock/tests/general/oauth2/user')
Http::get('/v1/mock/tests/general/oauth2/user')
->desc('OAuth2 User')
->groups(['mock'])
->label('scope', 'public')
@@ -121,7 +121,7 @@ App::get('/v1/mock/tests/general/oauth2/user')
]);
});
App::get('/v1/mock/tests/general/oauth2/user-unverified')
Http::get('/v1/mock/tests/general/oauth2/user-unverified')
->desc('OAuth2 User Unverified')
->groups(['mock'])
->label('scope', 'public')
@@ -142,7 +142,7 @@ App::get('/v1/mock/tests/general/oauth2/user-unverified')
]);
});
App::get('/v1/mock/tests/general/oauth2/success')
Http::get('/v1/mock/tests/general/oauth2/success')
->desc('OAuth2 Success')
->groups(['mock'])
->label('scope', 'public')
@@ -155,7 +155,7 @@ App::get('/v1/mock/tests/general/oauth2/success')
]);
});
App::get('/v1/mock/tests/general/oauth2/failure')
Http::get('/v1/mock/tests/general/oauth2/failure')
->desc('OAuth2 Failure')
->groups(['mock'])
->label('scope', 'public')
@@ -170,7 +170,7 @@ App::get('/v1/mock/tests/general/oauth2/failure')
]);
});
App::post('/v1/mock/api-key-unprefixed')
Http::post('/v1/mock/api-key-unprefixed')
->desc('Create API Key (without standard prefix)')
->groups(['mock', 'api', 'projects'])
->label('scope', 'public')
@@ -191,7 +191,7 @@ App::post('/v1/mock/api-key-unprefixed')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$scopes = array_keys(Config::getParam('scopes'));
$scopes = array_keys(Config::getParam('projectScopes'));
$key = new Document([
'$id' => ID::unique(),
@@ -200,9 +200,6 @@ App::post('/v1/mock/api-key-unprefixed')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
// TODO: @hmacr Remove `projectInternalId` and `projectId` column writes before deleting the column.
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
@@ -223,7 +220,7 @@ App::post('/v1/mock/api-key-unprefixed')
->dynamic($key, Response::MODEL_KEY);
});
App::get('/v1/mock/github/callback')
Http::get('/v1/mock/github/callback')
->desc('Create installation document using GitHub installation id')
->groups(['mock', 'api', 'vcs'])
->label('scope', 'public')
@@ -283,12 +280,12 @@ App::get('/v1/mock/github/callback')
]);
});
App::shutdown()
Http::shutdown()
->groups(['mock'])
->inject('utopia')
->inject('response')
->inject('request')
->action(function (App $utopia, Response $response, Request $request) {
->action(function (Http $utopia, Response $response, Request $request) {
$result = [];
$route = $utopia->getRoute();
+49 -29
View File
@@ -21,7 +21,6 @@ use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
@@ -31,6 +30,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Http;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Validator\WhiteList;
@@ -74,7 +74,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
return $label;
};
App::init()
Http::init()
->groups(['api'])
->inject('utopia')
->inject('request')
@@ -89,7 +89,7 @@ App::init()
->inject('team')
->inject('apiKey')
->inject('authorization')
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
$route = $utopia->getRoute();
/**
@@ -157,10 +157,6 @@ App::init()
// Step 5: API Key Authentication
if (!empty($apiKey)) {
// Verify no user session exists simultaneously
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if key is expired
if ($apiKey->isExpired()) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
@@ -189,23 +185,38 @@ App::init()
}
// For standard keys, update last accessed time
if ($apiKey->getType() === API_KEY_STANDARD) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
if (\in_array($apiKey->getType(), [API_KEY_STANDARD, API_KEY_ORGANIZATION, API_KEY_ACCOUNT])) {
$dbKey = null;
if (!empty($apiKey->getProjectId())) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getUserId())) {
$dbKey = $user->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getTeamId())) {
$dbKey = $team->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
}
if (!$dbKey) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$updates = new Document();
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$dbKey->setAttribute('accessedAt', DateTime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
$updates->setAttribute('accessedAt', DateTime::now());
}
$sdkValidator = new WhiteList($servers, true);
@@ -216,12 +227,21 @@ App::init()
if (!in_array($sdk, $sdks)) {
$sdks[] = $sdk;
$dbKey->setAttribute('sdks', $sdks);
/** Update access time as well */
$dbKey->setAttribute('accessedAt', Datetime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
$updates->setAttribute('sdks', $sdks);
$updates->setAttribute('accessedAt', Datetime::now());
}
}
if (!$updates->isEmpty()) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('keys', $dbKey->getId(), $updates));
if (!empty($apiKey->getProjectId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
} elseif (!empty($apiKey->getUserId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('users', $user->getId()));
} elseif (!empty($apiKey->getTeamId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId()));
}
}
@@ -336,7 +356,7 @@ App::init()
}
});
App::init()
Http::init()
->groups(['api'])
->inject('utopia')
->inject('request')
@@ -362,7 +382,7 @@ App::init()
->inject('telemetry')
->inject('platform')
->inject('authorization')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
->action(function (Http $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
$route = $utopia->getRoute();
@@ -572,7 +592,7 @@ App::init()
}
});
App::init()
Http::init()
->groups(['session'])
->inject('user')
->inject('request')
@@ -592,14 +612,14 @@ App::init()
* Delete older sessions if the number of sessions have crossed
* the session limit set for the project
*/
App::shutdown()
Http::shutdown()
->groups(['session'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
->action(function (Http $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
$session = $response->getPayload();
$userId = $session['userId'] ?? '';
@@ -626,7 +646,7 @@ App::shutdown()
$dbForProject->purgeCachedDocument('users', $userId);
});
App::shutdown()
Http::shutdown()
->groups(['api'])
->inject('utopia')
->inject('request')
@@ -647,7 +667,7 @@ App::shutdown()
->inject('authorization')
->inject('timelimit')
->inject('eventProcessor')
->action(function (App $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor) use ($parseLabel) {
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -872,7 +892,7 @@ App::shutdown()
}
});
App::init()
Http::init()
->groups(['usage'])
->action(function () {
if (System::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
+4 -4
View File
@@ -4,14 +4,14 @@ use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Documents\User;
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;
use Utopia\Http;
use Utopia\System\System;
App::init()
Http::init()
->groups(['mfaProtected'])
->inject('session')
->action(function (Document $session) {
@@ -30,14 +30,14 @@ App::init()
}
});
App::init()
Http::init()
->groups(['auth'])
->inject('utopia')
->inject('request')
->inject('project')
->inject('geodb')
->inject('authorization')
->action(function (App $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist);
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Http;
App::get('/versions')
Http::get('/versions')
->desc('Get Version')
->groups(['home', 'web'])
->label('scope', 'public')
+46 -14
View File
@@ -11,7 +11,6 @@ use Swoole\Http\Server;
use Swoole\Process;
use Swoole\Table;
use Swoole\Timer;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Adapter\SQL as AuditAdapterSQL;
use Utopia\Audit\Audit;
@@ -27,6 +26,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Http;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Pools\Group;
@@ -100,11 +100,19 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
$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;
} else {
foreach (\explode(',', System::getEnv('_APP_DOMAIN_FUNCTIONS')) as $riskyDomain) {
if (empty($riskyDomain)) {
continue;
}
if (str_ends_with($domain, $riskyDomain)) {
$risky = true;
break;
}
}
}
if ($risky) {
@@ -173,7 +181,7 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
include __DIR__ . '/controllers/general.php';
function createDatabase(App $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
{
$max = 10;
$sleep = 1;
@@ -246,12 +254,12 @@ function createDatabase(App $app, string $resourceKey, string $dbName, array $co
}
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
$app = new App('UTC');
$app = new Http('UTC');
go(function () use ($register, $app) {
$pools = $register->get('pools');
/** @var Group $pools */
App::setResource('pools', fn () => $pools);
Http::setResource('pools', fn () => $pools);
/** @var array $collections */
$collections = Config::getParam('collections', []);
@@ -433,8 +441,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
});
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
App::setResource('swooleRequest', fn () => $swooleRequest);
App::setResource('swooleResponse', fn () => $swooleResponse);
Http::setResource('swooleRequest', fn () => $swooleRequest);
Http::setResource('swooleResponse', fn () => $swooleResponse);
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
@@ -451,12 +459,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
return;
}
$app = new App('UTC');
$app = new Http('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);
Http::setResource('pools', fn () => $pools);
try {
$authorization = $app->getResource('authorization');
@@ -538,7 +546,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$swooleResponse->setStatusCode(500);
$output = ((App::isDevelopment())) ? [
$output = ((Http::isDevelopment())) ? [
'message' => 'Error: ' . $th->getMessage(),
'code' => 500,
'file' => $th->getFile(),
@@ -559,8 +567,8 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
App::setResource('pools', fn () => $pools);
$app = new App('UTC');
Http::setResource('pools', fn () => $pools);
$app = new Http('UTC');
/** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform');
@@ -591,9 +599,33 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
$sum = count($results);
foreach ($results as $document) {
$domain = $document->getAttribute('domain');
if (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS')) || str_ends_with($domain, System::getEnv('_APP_DOMAIN_SITES'))) {
$denyDomains = [];
$denyEnvVars = [
System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''),
System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
System::getEnv('_APP_DOMAIN_SITES', ''),
];
foreach ($denyEnvVars as $denyEnvVar) {
foreach (\explode(',', $denyEnvVar) as $denyDomain) {
if (empty($denyDomain)) {
continue;
}
$denyDomains[] = $denyDomain;
}
}
$isDenyDomain = false;
foreach ($denyDomains as $denyDomain) {
if (str_ends_with($domain, $denyDomain)) {
$isDenyDomain = true;
}
}
if ($isDenyDomain) {
continue;
}
$domains->set(md5($domain), ['value' => 1]);
}
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
+3 -1
View File
@@ -22,7 +22,9 @@ Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapt
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
Config::load('projectScopes', __DIR__ . '/../config/scopes/project.php', $configAdapter);
Config::load('organizationScopes', __DIR__ . '/../config/scopes/organization.php', $configAdapter);
Config::load('accountScopes', __DIR__ . '/../config/scopes/account.php', $configAdapter);
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
+10
View File
@@ -5,6 +5,8 @@ use Appwrite\Platform\Modules\Compute\Specification;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
const APP_VIEWS_DIR = __DIR__ . '/../views';
// Email
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = ''; // Default security email address
@@ -194,6 +196,7 @@ const DELETE_TYPE_SITES = 'sites';
const DELETE_TYPE_FUNCTIONS = 'functions';
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
const DELETE_TYPE_USERS = 'users';
const DELETE_TYPE_TEAMS = 'teams';
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_EXECUTIONS_LIMIT = 'executionsLimit';
@@ -249,6 +252,8 @@ const MESSAGE_TYPE_PUSH = 'push';
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_DYNAMIC = 'dynamic';
const API_KEY_ORGANIZATION = 'organization';
const API_KEY_ACCOUNT = 'account';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
@@ -366,6 +371,7 @@ const RESOURCE_TYPE_TOPICS = 'topics';
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
const RESOURCE_TYPE_EXECUTIONS = 'executions';
const RESOURCE_TYPE_VCS = 'vcs';
// Resource types for Tokens
const TOKENS_RESOURCE_TYPE_FILES = 'files';
@@ -380,3 +386,7 @@ const SCHEDULE_RESOURCE_TYPE_MESSAGE = 'message';
/** Preview cookie */
const COOKIE_NAME_PREVIEW = 'a_jwt_console';
// Cache Reconnect
const CACHE_RECONNECT_MAX_RETRIES = 2;
const CACHE_RECONNECT_RETRY_DELAY = 1000;
+31
View File
@@ -433,3 +433,34 @@ Database::addFilter(
return $value;
}
);
Database::addFilter(
'subQueryOrganizationKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['teams']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryAccountKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['users']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
+17 -1
View File
@@ -20,11 +20,15 @@ use Appwrite\Utopia\Response\Model\AttributeInteger;
use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeLine;
use Appwrite\Utopia\Response\Model\AttributeList;
use Appwrite\Utopia\Response\Model\AttributeLongtext;
use Appwrite\Utopia\Response\Model\AttributeMediumtext;
use Appwrite\Utopia\Response\Model\AttributePoint;
use Appwrite\Utopia\Response\Model\AttributePolygon;
use Appwrite\Utopia\Response\Model\AttributeRelationship;
use Appwrite\Utopia\Response\Model\AttributeString;
use Appwrite\Utopia\Response\Model\AttributeText;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\AttributeVarchar;
use Appwrite\Utopia\Response\Model\AuthProvider;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Branch;
@@ -41,11 +45,15 @@ use Appwrite\Utopia\Response\Model\ColumnInteger;
use Appwrite\Utopia\Response\Model\ColumnIP;
use Appwrite\Utopia\Response\Model\ColumnLine;
use Appwrite\Utopia\Response\Model\ColumnList;
use Appwrite\Utopia\Response\Model\ColumnLongtext;
use Appwrite\Utopia\Response\Model\ColumnMediumtext;
use Appwrite\Utopia\Response\Model\ColumnPoint;
use Appwrite\Utopia\Response\Model\ColumnPolygon;
use Appwrite\Utopia\Response\Model\ColumnRelationship;
use Appwrite\Utopia\Response\Model\ColumnString;
use Appwrite\Utopia\Response\Model\ColumnText;
use Appwrite\Utopia\Response\Model\ColumnURL;
use Appwrite\Utopia\Response\Model\ColumnVarchar;
use Appwrite\Utopia\Response\Model\ConsoleVariables;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
@@ -177,7 +185,7 @@ Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_L
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, false));
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
Response::setModel(new BaseList('Platforms List', Response::MODEL_PLATFORM_LIST, 'platforms', Response::MODEL_PLATFORM, true, false));
@@ -222,6 +230,10 @@ Response::setModel(new AttributeRelationship());
Response::setModel(new AttributePoint());
Response::setModel(new AttributeLine());
Response::setModel(new AttributePolygon());
Response::setModel(new AttributeVarchar());
Response::setModel(new AttributeText());
Response::setModel(new AttributeMediumtext());
Response::setModel(new AttributeLongtext());
// Table API Models
Response::setModel(new Table());
@@ -240,6 +252,10 @@ Response::setModel(new ColumnRelationship());
Response::setModel(new ColumnPoint());
Response::setModel(new ColumnLine());
Response::setModel(new ColumnPolygon());
Response::setModel(new ColumnVarchar());
Response::setModel(new ColumnText());
Response::setModel(new ColumnMediumtext());
Response::setModel(new ColumnLongtext());
Response::setModel(new Index());
Response::setModel(new ColumnIndex());
Response::setModel(new Row());
+17 -5
View File
@@ -8,7 +8,6 @@ use Appwrite\URL\URL as AppwriteURL;
use MaxMind\Db\Reader;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Database\PDOProxy;
use Utopia\App;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
@@ -18,22 +17,26 @@ use Utopia\Database\Adapter\SQL;
use Utopia\Database\PDO;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Logger\Adapter\AppSignal;
use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Logger;
use Utopia\Pools\Adapter\Stack as StackPool;
use Utopia\Pools\Adapter\Swoole as SwoolePool;
use Utopia\Pools\Group;
use Utopia\Pools\Pool;
use Utopia\Queue;
use Utopia\Registry\Registry;
use Utopia\System\System;
global $register;
$register = new Registry();
App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
Http::setMode(System::getEnv('_APP_ENV', Http::MODE_TYPE_PRODUCTION));
if (!App::isProduction()) {
if (!Http::isProduction()) {
// Allow specific domains to skip public domain validation in dev environment
// Useful for existing tests involving webhooks
PublicDomain::allow(['request-catcher-sms']);
@@ -285,7 +288,9 @@ $register->set('pools', function () {
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
};
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
$pool = new Pool($poolAdapter, $name, $poolSize, function () use ($type, $resource, $dsn) {
// Get Adapter
switch ($type) {
case 'database':
@@ -309,10 +314,17 @@ $register->set('pools', function () {
default => null
};
case 'cache':
return match ($dsn->getScheme()) {
$adapter = match ($dsn->getScheme()) {
'redis' => new RedisCache($resource()),
default => null
};
if ($adapter !== null) {
$adapter->setMaxRetries(CACHE_RECONNECT_MAX_RETRIES);
$adapter->setRetryDelay(CACHE_RECONNECT_RETRY_DELAY);
}
return $adapter;
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
}
+187 -91
View File
@@ -10,6 +10,7 @@ use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Execution;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
@@ -31,7 +32,6 @@ use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit;
use Utopia\Auth\Hashes\Argon2;
@@ -52,6 +52,7 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
@@ -74,114 +75,118 @@ use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub;
// Runtime Execution
App::setResource('log', fn () => new Log());
App::setResource('logger', function ($register) {
Http::setResource('log', fn () => new Log());
Http::setResource('logger', function ($register) {
return $register->get('logger');
}, ['register']);
App::setResource('hooks', function ($register) {
Http::setResource('hooks', function ($register) {
return $register->get('hooks');
}, ['register']);
App::setResource('register', fn () => $register);
App::setResource('locale', function () {
global $register;
Http::setResource('register', fn () => $register);
Http::setResource('locale', function () {
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
$locale->setFallback(System::getEnv('_APP_LOCALE', 'en'));
return $locale;
});
App::setResource('localeCodes', function () {
Http::setResource('localeCodes', function () {
return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []));
});
// Queues
App::setResource('publisher', function (Group $pools) {
Http::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
App::setResource('publisherDatabases', function (Publisher $publisher) {
Http::setResource('publisherDatabases', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('publisherFunctions', function (Publisher $publisher) {
Http::setResource('publisherFunctions', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('publisherMigrations', function (Publisher $publisher) {
Http::setResource('publisherMigrations', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('publisherStatsUsage', function (Publisher $publisher) {
Http::setResource('publisherStatsUsage', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('publisherMails', function (Publisher $publisher) {
Http::setResource('publisherMails', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('publisherDeletes', function (Publisher $publisher) {
Http::setResource('publisherDeletes', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('publisherMessaging', function (Publisher $publisher) {
Http::setResource('publisherMessaging', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('publisherWebhooks', function (Publisher $publisher) {
Http::setResource('publisherWebhooks', function (Publisher $publisher) {
return $publisher;
}, ['publisher']);
App::setResource('queueForMessaging', function (Publisher $publisher) {
Http::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
App::setResource('queueForMails', function (Publisher $publisher) {
Http::setResource('queueForMails', function (Publisher $publisher) {
return new Mail($publisher);
}, ['publisher']);
App::setResource('queueForBuilds', function (Publisher $publisher) {
Http::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
App::setResource('queueForScreenshots', function (Publisher $publisher) {
Http::setResource('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
App::setResource('queueForDatabase', function (Publisher $publisher) {
Http::setResource('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
App::setResource('queueForDeletes', function (Publisher $publisher) {
Http::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
App::setResource('queueForEvents', function (Publisher $publisher) {
Http::setResource('queueForEvents', function (Publisher $publisher) {
return new Event($publisher);
}, ['publisher']);
App::setResource('queueForWebhooks', function (Publisher $publisher) {
Http::setResource('queueForWebhooks', function (Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
App::setResource('queueForRealtime', function () {
Http::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
App::setResource('queueForStatsUsage', function (Publisher $publisher) {
Http::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
App::setResource('queueForAudits', function (Publisher $publisher) {
Http::setResource('queueForAudits', function (Publisher $publisher) {
return new AuditEvent($publisher);
}, ['publisher']);
App::setResource('queueForFunctions', function (Publisher $publisher) {
Http::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
App::setResource('eventProcessor', function () {
Http::setResource('queueForExecutions', function (Publisher $publisher) {
return new Execution($publisher);
}, ['publisher']);
Http::setResource('eventProcessor', function () {
return new EventProcessor();
}, []);
App::setResource('queueForCertificates', function (Publisher $publisher) {
Http::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
App::setResource('queueForMigrations', function (Publisher $publisher) {
Http::setResource('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
App::setResource('queueForStatsResources', function (Publisher $publisher) {
Http::setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
/**
* Platform configuration
*/
App::setResource('platform', function () {
Http::setResource('platform', function () {
return Config::getParam('platform', []);
}, []);
/**
* List of allowed request hostnames for the request.
*/
App::setResource('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
Http::setResource('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
$allowed = [...($platform['hostnames'] ?? [])];
/* Add platform configured hostnames */
@@ -197,15 +202,26 @@ App::setResource('allowedHostnames', function (array $platform, Document $projec
}
$originHostname = parse_url($request->getOrigin(), PHP_URL_HOST);
$refererHostname = parse_url($request->getReferer(), PHP_URL_HOST);
$hostname = $originHostname;
if (empty($hostname)) {
$hostname = $refererHostname;
}
/* Add request hostname for preflight requests */
if ($request->getMethod() === 'OPTIONS') {
$allowed[] = $originHostname;
$allowed[] = $hostname;
}
/* Allow the request origin if a dev key or rule is found */
if ((!$rule->isEmpty() || !$devKey->isEmpty()) && !empty($originHostname)) {
$allowed[] = $originHostname;
/* Allow the request origin of rule */
if (!$rule->isEmpty() && !empty($rule->getAttribute('domain', ''))) {
$allowed[] = $rule->getAttribute('domain', '');
}
/* Allow the request origin if a dev key is found */
if (!$devKey->isEmpty() && !empty($hostname)) {
$allowed[] = $hostname;
}
return array_unique($allowed);
@@ -214,7 +230,7 @@ App::setResource('allowedHostnames', function (array $platform, Document $projec
/**
* List of allowed request schemes for the request.
*/
App::setResource('allowedSchemes', function (Document $project) {
Http::setResource('allowedSchemes', function (Document $project) {
$allowed = [];
if (!$project->isEmpty() && $project->getId() !== 'console') {
@@ -234,8 +250,13 @@ App::setResource('allowedSchemes', function (Document $project) {
/**
* Rule associated with a request origin.
*/
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
Http::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
if (empty($domain)) {
$domain = \parse_url($request->getReferer(), PHP_URL_HOST);
}
if (empty($domain)) {
return new Document();
}
@@ -252,7 +273,24 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
]) ?? new Document();
});
if ($rule->getAttribute('projectInternalId') !== $project->getSequence()) {
$permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence();
// Temporary implementation until custom wildcard domains are an official feature
// Allow trusted projects; Used for Console (website) previews
if (!$permitsCurrentProject && !$rule->isEmpty() && !empty($rule->getAttribute('projectId', ''))) {
$trustedProjects = [];
foreach (\explode(',', System::getEnv('_APP_CONSOLE_TRUSTED_PROJECTS', '')) as $trustedProject) {
if (empty($trustedProject)) {
continue;
}
$trustedProjects[] = $trustedProject;
}
if (\in_array($rule->getAttribute('projectId', ''), $trustedProjects)) {
$permitsCurrentProject = true;
}
}
if (!$permitsCurrentProject) {
return new Document();
}
@@ -262,7 +300,7 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
/**
* CORS service
*/
App::setResource('cors', fn (array $allowedHostnames) => new Cors(
Http::setResource('cors', fn (array $allowedHostnames) => new Cors(
$allowedHostnames,
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: [
@@ -311,21 +349,21 @@ App::setResource('cors', fn (array $allowedHostnames) => new Cors(
],
), ['allowedHostnames']);
App::setResource('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
Http::setResource('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
if (!$devKey->isEmpty()) {
return new URL();
}
return new Origin($allowedHostnames, $allowedSchemes);
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
App::setResource('redirectValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
Http::setResource('redirectValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
if (!$devKey->isEmpty()) {
return new URL();
}
return new Redirect($allowedHostnames, $allowedSchemes);
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
Http::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
/**
* Handles user authentication and session validation.
*
@@ -343,6 +381,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
* overwriting the previous value.
* 7. If account API key is passed, use user of the account API key as long as user ID header matches too
*/
$authorization->setDefaultStatus(true);
@@ -419,12 +458,17 @@ App::setResource('user', function (string $mode, Document $project, Document $co
// }
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_JWT_AND_COOKIE_SET);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwt->decode($authJWT);
} catch (JWTException $error) {
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
}
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
if ($mode === APP_MODE_ADMIN) {
@@ -440,13 +484,41 @@ App::setResource('user', function (string $mode, Document $project, Document $co
}
}
}
// Account based on account API key
$accountKey = $request->getHeader('x-appwrite-key', '');
$accountKeyUserId = $request->getHeader('x-appwrite-user', '');
if (!empty($accountKeyUserId) && !empty($accountKey)) {
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
$accountKeyUser = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId));
if (!$accountKeyUser->isEmpty()) {
$key = $accountKeyUser->find(
key: 'secret',
find: $accountKey,
subject: 'keys'
);
if (!empty($key)) {
$expire = $key->getAttribute('expire');
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
throw new Exception(Exception::ACCOUNT_KEY_EXPIRED);
}
$user = $accountKeyUser;
}
}
}
$dbForProject->setMetadata('user', $user->getId());
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
App::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
Http::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var Utopia\Database\Document $console */
@@ -462,7 +534,7 @@ App::setResource('project', function ($dbForPlatform, $request, $console, $autho
return $project;
}, ['dbForPlatform', 'request', 'console', 'authorization']);
App::setResource('session', function (User $user, Store $store, Token $proofForToken) {
Http::setResource('session', function (User $user, Store $store, Token $proofForToken) {
if ($user->isEmpty()) {
return;
}
@@ -483,11 +555,11 @@ App::setResource('session', function (User $user, Store $store, Token $proofForT
return;
}, ['user', 'store', 'proofForToken']);
App::setResource('store', function (): Store {
Http::setResource('store', function (): Store {
return new Store();
});
App::setResource('proofForPassword', function (): Password {
Http::setResource('proofForPassword', function (): Password {
$hash = new Argon2();
$hash
->setMemoryCost(7168)
@@ -501,27 +573,27 @@ App::setResource('proofForPassword', function (): Password {
return $password;
});
App::setResource('proofForToken', function (): Token {
Http::setResource('proofForToken', function (): Token {
$token = new Token();
$token->setHash(new Sha());
return $token;
});
App::setResource('proofForCode', function (): Code {
Http::setResource('proofForCode', function (): Code {
$code = new Code();
$code->setHash(new Sha());
return $code;
});
App::setResource('console', function () {
Http::setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
App::setResource('authorization', function () {
Http::setResource('authorization', function () {
return new Authorization();
}, []);
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Authorization $authorization) {
Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -763,7 +835,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'queueForStatsUsage', 'authorization']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
Http::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, $cache);
@@ -782,7 +854,7 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authoriz
return $database;
}, ['pools', 'cache', 'authorization']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
Http::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
$databases = [];
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
@@ -838,7 +910,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
Http::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
@@ -867,14 +939,14 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
};
}, ['pools', 'cache', 'authorization']);
App::setResource('audit', function ($dbForProject) {
Http::setResource('audit', function ($dbForProject) {
$adapter = new AdapterDatabase($dbForProject);
return new Audit($adapter);
}, ['dbForProject']);
App::setResource('telemetry', fn () => new NoTelemetry());
Http::setResource('telemetry', fn () => new NoTelemetry());
App::setResource('cache', function (Group $pools, Telemetry $telemetry) {
Http::setResource('cache', function (Group $pools, Telemetry $telemetry) {
$list = Config::getParam('pools-cache', []);
$adapters = [];
@@ -887,7 +959,7 @@ App::setResource('cache', function (Group $pools, Telemetry $telemetry) {
return $cache;
}, ['pools', 'telemetry']);
App::setResource('redis', function () {
Http::setResource('redis', function () {
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
$pass = System::getEnv('_APP_REDIS_PASS', '');
@@ -902,28 +974,28 @@ App::setResource('redis', function () {
return $redis;
});
App::setResource('timelimit', function (\Redis $redis) {
Http::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 (Telemetry $telemetry) {
Http::setResource('deviceForLocal', function (Telemetry $telemetry) {
return new Device\Telemetry($telemetry, new Local());
}, ['telemetry']);
App::setResource('deviceForFiles', function ($project, Telemetry $telemetry) {
Http::setResource('deviceForFiles', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForSites', function ($project, Telemetry $telemetry) {
Http::setResource('deviceForSites', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForMigrations', function ($project, Telemetry $telemetry) {
Http::setResource('deviceForMigrations', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForFunctions', function ($project, Telemetry $telemetry) {
Http::setResource('deviceForFunctions', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
App::setResource('deviceForBuilds', function ($project, Telemetry $telemetry) {
Http::setResource('deviceForBuilds', function ($project, Telemetry $telemetry) {
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
}, ['project', 'telemetry']);
@@ -1027,7 +1099,7 @@ function getDevice(string $root, string $connection = ''): Device
}
}
App::setResource('mode', function ($request) {
Http::setResource('mode', function ($request) {
/** @var Appwrite\Utopia\Request $request */
/**
@@ -1038,18 +1110,18 @@ App::setResource('mode', function ($request) {
return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
}, ['request']);
App::setResource('geodb', function ($register) {
Http::setResource('geodb', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('geodb');
}, ['register']);
App::setResource('passwordsDictionary', function ($register) {
Http::setResource('passwordsDictionary', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('passwordsDictionary');
}, ['register']);
App::setResource('servers', function () {
Http::setResource('servers', function () {
$platforms = Config::getParam('sdks');
$server = $platforms[APP_SDK_PLATFORM_SERVER];
@@ -1060,11 +1132,11 @@ App::setResource('servers', function () {
return $languages;
});
App::setResource('promiseAdapter', function ($register) {
Http::setResource('promiseAdapter', function ($register) {
return $register->get('promiseAdapter');
}, ['register']);
App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
Http::setResource('schema', function ($utopia, $dbForProject, $authorization) {
$complexity = function (int $complexity, array $args) {
$queries = Query::parseQueries($args['queries'] ?? []);
@@ -1151,11 +1223,11 @@ App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
);
}, ['utopia', 'dbForProject', 'authorization']);
App::setResource('gitHub', function (Cache $cache) {
Http::setResource('gitHub', function (Cache $cache) {
return new VcsGitHub($cache);
}, ['cache']);
App::setResource('requestTimestamp', function ($request) {
Http::setResource('requestTimestamp', function ($request) {
//TODO: Move this to the Request class itself
$timestampHeader = $request->getHeader('x-appwrite-timestamp');
$requestTimestamp = null;
@@ -1169,15 +1241,15 @@ App::setResource('requestTimestamp', function ($request) {
return $requestTimestamp;
}, ['request']);
App::setResource('plan', function (array $plan = []) {
Http::setResource('plan', function (array $plan = []) {
return [];
});
App::setResource('smsRates', function () {
Http::setResource('smsRates', function () {
return [];
});
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
Http::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
// Check if given key match project's development keys
@@ -1221,13 +1293,13 @@ App::setResource('devKey', function (Request $request, Document $project, array
return $key;
}, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']);
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request, Authorization $authorization) {
Http::setResource('team', function (Document $project, Database $dbForPlatform, Http $utopia, Request $request, Authorization $authorization) {
$teamInternalId = '';
if ($project->getId() !== 'console') {
$teamInternalId = $project->getAttribute('teamInternalId', '');
} else {
$route = $utopia->match($request);
$path = $route->getPath();
$path = !empty($route) ? $route->getPath() : $request->getURI();
if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI();
$pid = explode('/', $uri)[3];
@@ -1258,15 +1330,15 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
return $team;
}, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']);
App::setResource(
Http::setResource(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
Http::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
$allowed = false;
if (App::isDevelopment()) {
if (Http::isDevelopment()) {
$allowed = true;
} elseif (!\is_null($apiKey) && $apiKey->getHostnameOverride() === true) {
$allowed = true;
@@ -1282,19 +1354,43 @@ App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
return '';
}, ['request', 'apiKey']);
App::setResource('apiKey', function (Request $request, Document $project): ?Key {
Http::setResource('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
$key = $request->getHeader('x-appwrite-key');
if (empty($key)) {
return null;
}
return Key::decode($project, $key);
}, ['request', 'project']);
$key = Key::decode($project, $team, $user, $key);
App::setResource('executor', fn () => new Executor());
$userHeader = $request->getHeader('x-appwrite-user');
$organizationHeader = $request->getHeader('x-appwrite-organization');
$projectHeader = $request->getHeader('x-appwrite-project');
App::setResource('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
if (!empty($key->getProjectId())) {
if (empty($projectHeader) || $projectHeader !== $key->getProjectId()) {
throw new Exception(Exception::PROJECT_ID_MISSING);
}
}
if (!empty($key->getUserId())) {
if (empty($userHeader) || $userHeader !== $key->getUserId()) {
throw new Exception(Exception::USER_ID_MISSING);
}
}
if (!empty($key->getTeamId())) {
if (empty($organizationHeader) || $organizationHeader !== $key->getTeamId()) {
throw new Exception(Exception::ORGANIZATION_ID_MISSING);
}
}
return $key;
}, ['request', 'project', 'team', 'user']);
Http::setResource('executor', fn () => new Executor());
Http::setResource('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
$tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
@@ -1358,11 +1454,11 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A
return new Document([]);
}, ['project', 'dbForProject', 'request', 'authorization']);
App::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
Http::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
return new TransactionState($dbForProject, $authorization);
}, ['dbForProject', 'authorization']);
App::setResource('executionsRetentionCount', function (Document $project, array $plan) {
Http::setResource('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
+118 -40
View File
@@ -16,7 +16,6 @@ use Swoole\Table;
use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
@@ -34,6 +33,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
@@ -431,10 +431,24 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
]
];
$server->send($realtime->getSubscribers($event), json_encode([
'type' => 'event',
'data' => $event['data']
]));
$subscribers = $realtime->getSubscribers($event);
$groups = [];
foreach ($subscribers as $id => $matched) {
$key = implode(',', array_keys($matched));
$groups[$key]['ids'][] = $id;
$groups[$key]['subscriptions'] = array_keys($matched);
}
foreach ($groups as $group) {
$data = $event['data'];
$data['subscriptions'] = $group['subscriptions'];
$server->send($group['ids'], json_encode([
'type' => 'event',
'data' => $data
]));
}
}
});
@@ -473,33 +487,63 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$user = $database->getDocument('users', $userId);
$roles = $user->getRoles($database->getAuthorization());
$channels = $realtime->connections[$connection]['channels'];
$queries = $realtime->connections[$connection]['queries'] ?? [];
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$meta = $realtime->getSubscriptionMetadata($connection);
$realtime->unsubscribe($connection);
$realtime->subscribe($projectId, $connection, $roles, $channels, $queries);
foreach ($meta as $subscriptionId => $subscription) {
$queries = Query::parseQueries($subscription['queries'] ?? []);
$realtime->subscribe(
$projectId,
$connection,
$subscriptionId,
$roles,
$subscription['channels'] ?? [],
$queries
);
}
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
}
}
$receivers = $realtime->getSubscribers($event);
if (App::isDevelopment() && !empty($receivers)) {
if (Http::isDevelopment() && !empty($receivers)) {
Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers));
Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode($receivers));
Console::log("[Debug][Worker {$workerId}] Connection IDs: " . json_encode(array_keys($receivers)));
Console::log("[Debug][Worker {$workerId}] Matched: " . json_encode(array_values($receivers)));
Console::log("[Debug][Worker {$workerId}] Event: " . $payload);
}
$server->send(
$receivers,
json_encode([
'type' => 'event',
'data' => $event['data']
])
);
// Group connections by matched subscription IDs for batch sending
$groups = [];
foreach ($receivers as $id => $matched) {
$key = implode(',', array_keys($matched));
$groups[$key]['ids'][] = $id;
$groups[$key]['subscriptions'] = array_keys($matched);
}
if (($num = count($receivers)) > 0) {
$register->get('telemetry.messageSentCounter')->add($num);
$stats->incr($event['project'], 'messages', $num);
$total = 0;
foreach ($groups as $group) {
$data = $event['data'];
$data['subscriptions'] = $group['subscriptions'];
$server->send($group['ids'], json_encode([
'type' => 'event',
'data' => $data
]));
$total += count($group['ids']);
}
if ($total > 0) {
$register->get('telemetry.messageSentCounter')->add($total);
$stats->incr($event['project'], 'messages', $total);
}
});
} catch (Throwable $th) {
@@ -516,15 +560,15 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
});
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) {
$app = new App('UTC');
$app = new Http('UTC');
$request = new Request($request);
$response = new Response(new SwooleResponse());
Console::info("Connection open (user: {$connection})");
App::setResource('pools', fn () => $register->get('pools'));
App::setResource('request', fn () => $request);
App::setResource('response', fn () => $response);
Http::setResource('pools', fn () => $register->get('pools'));
Http::setResource('request', fn () => $request);
Http::setResource('response', fn () => $response);
try {
/** @var Document $project */
@@ -580,11 +624,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$roles = $user->getRoles($authorization);
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
try {
$queries = Realtime::convertQueries($request->getQuery('queries', []));
} catch (QueryException $e) {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
}
/**
* Channels Check
@@ -593,7 +632,32 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
}
$realtime->subscribe($project->getId(), $connection, $roles, $channels, $queries);
$names = array_keys($channels);
try {
$subscriptions = Realtime::constructSubscriptions(
$names,
fn ($channel) => $request->getQuery($channel, null)
);
} catch (QueryException $e) {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
}
$mapping = [];
foreach ($subscriptions as $index => $subscription) {
$subscriptionId = ID::unique();
$realtime->subscribe(
$project->getId(),
$connection,
$subscriptionId,
$roles,
$subscription['channels'],
$subscription['queries']
);
$mapping[$index] = $subscriptionId;
}
$realtime->connections[$connection]['authorization'] = $authorization;
@@ -602,8 +666,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->send([$connection], json_encode([
'type' => 'connected',
'data' => [
'channels' => array_keys($channels),
'queries' => $queries,
'channels' => $names,
'subscriptions' => $mapping,
'user' => $user
]
]));
@@ -630,7 +694,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$message = $th->getMessage();
// sanitize 0 && 5xx errors
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
$message = 'Error: Server Error';
}
@@ -645,7 +709,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->send([$connection], json_encode($response));
$server->close($connection, $code);
if (App::isDevelopment()) {
if (Http::isDevelopment()) {
Console::error('[Error] Connection Error');
Console::error('[Error] Code: ' . $response['data']['code']);
Console::error('[Error] Message: ' . $response['data']['message']);
@@ -733,15 +797,29 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
}
$roles = $user->getRoles($database->getAuthorization());
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
// Preserve authorization before subscribe overwrites the connection array
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
$queries = $realtime->connections[$connection]['queries'];
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels, $queries);
$meta = $realtime->getSubscriptionMetadata($connection);
$realtime->unsubscribe($connection);
if (!empty($projectId)) {
foreach ($meta as $subscriptionId => $subscription) {
$queries = Query::parseQueries($subscription['queries'] ?? []);
$realtime->subscribe(
$projectId,
$connection,
$subscriptionId,
$roles,
$subscription['channels'] ?? [],
$queries
);
}
}
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
@@ -770,7 +848,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$message = $th->getMessage();
// sanitize 0 && 5xx errors
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
$message = 'Error: Server Error';
}
+1 -1
View File
@@ -14,7 +14,7 @@ $image = $this->getParam('image', '');
$enableAssistant = $this->getParam('enableAssistant', false);
?>services:
traefik:
image: traefik:2.11
image: traefik:3.6
container_name: appwrite-traefik
<<: *x-logging
command:
+11
View File
@@ -9,6 +9,7 @@ use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Execution;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
@@ -44,13 +45,19 @@ use Utopia\Queue\Message;
use Utopia\Queue\Publisher;
use Utopia\Queue\Server;
use Utopia\Registry\Registry;
use Utopia\Span\Exporter;
use Utopia\Span\Span;
use Utopia\Span\Storage;
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
Runtime::enableCoroutine();
Span::setStorage(new Storage\Coroutine());
Span::addExporter(new Exporter\Stdout());
global $register;
Server::setResource('register', fn () => $register);
Server::setResource('authorization', function () {
@@ -352,6 +359,10 @@ Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
Server::setResource('queueForExecutions', function (Publisher $publisher) {
return new Execution($publisher);
}, ['publisher']);
Server::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php executions "$@"
+13 -13
View File
@@ -38,23 +38,23 @@
"ext-yaml": "*",
"ext-dom": "*",
"ext-redis": "*",
"ext-swoole": "*",
"ext-swoole": "6.*",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-zlib": "*",
"ext-sockets": "*",
"appwrite/php-runtimes": "0.19.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.*",
"utopia-php/abuse": "1.2.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "2.*",
"utopia-php/audit": "2.2.*",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "0.13.*",
"utopia-php/cache": "1.0.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "1.*",
"utopia-php/database": "4.*",
"utopia-php/database": "5.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.11.*",
"utopia-php/domains": "1.*",
"utopia-php/emails": "0.6.*",
"utopia-php/dns": "1.5.*",
"utopia-php/dsn": "0.2.1",
@@ -64,25 +64,25 @@
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.*",
"utopia-php/migration": "1.5.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
"utopia-php/pools": "1.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.15.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "1.*",
"utopia-php/system": "0.9.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/vcs": "0.13.*",
"utopia-php/system": "0.10.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/vcs": "1.*",
"utopia-php/websocket": "0.3.*",
"matomo/device-detector": "6.4.*",
"dragonmantank/cron-expression": "3.4.*",
"phpmailer/phpmailer": "6.9.*",
"chillerlan/php-qrcode": "4.4.*",
"adhocore/jwt": "1.1.*",
"spomky-labs/otphp": "10.0.*",
"spomky-labs/otphp": "^11.4.2",
"webonyx/graphql-php": "14.11.*",
"league/csv": "9.24.*",
"enshrined/svg-sanitize": "0.22.*"
@@ -91,7 +91,7 @@
"ext-fileinfo": "*",
"appwrite/sdk-generator": "*",
"phpunit/phpunit": "9.*",
"swoole/ide-helper": "5.1.2",
"swoole/ide-helper": "6.*",
"phpstan/phpstan": "1.8.*",
"textalk/websocket": "1.5.*",
"laravel/pint": "1.*",
Generated
+419 -476
View File
File diff suppressed because it is too large Load Diff
+58 -1
View File
@@ -12,7 +12,7 @@ x-logging: &x-logging
services:
traefik:
image: traefik:2.11
image: traefik:3.6
<<: *x-logging
container_name: appwrite-traefik
command:
@@ -98,6 +98,7 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
# - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
depends_on:
- mariadb
- redis
@@ -111,6 +112,7 @@ services:
- _APP_ENV
- _APP_EDITION
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOCALE
- _APP_COMPRESSION_ENABLED
- _APP_COMPRESSION_MIN_SIZE_BYTES
@@ -131,6 +133,7 @@ services:
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_TRUSTED_PROJECTS
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
@@ -226,6 +229,7 @@ services:
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
- _APP_MIGRATION_HOST
extra_hosts:
- "host.docker.internal:host-gateway"
@@ -300,6 +304,7 @@ services:
- _APP_LOGGING_CONFIG
- _APP_LOGGING_CONFIG_REALTIME
- _APP_DATABASE_SHARED_TABLES
- _APP_POOL_ADAPTER=swoole
appwrite-worker-audits:
entrypoint: worker-audits
@@ -317,6 +322,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -348,6 +354,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_DB_HOST
@@ -385,6 +392,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -442,6 +450,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -476,6 +485,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
@@ -502,6 +512,7 @@ services:
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_TRUSTED_PROJECTS
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
@@ -549,6 +560,7 @@ services:
# Basic
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOGGING_CONFIG
# Database
- _APP_OPENSSL_KEY_V1
@@ -606,6 +618,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -628,6 +641,37 @@ services:
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-executions:
entrypoint: worker-executions
<<: *x-logging
container_name: appwrite-worker-executions
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
redis:
condition: service_started
mariadb:
condition: service_started
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-functions:
entrypoint: worker-functions
<<: *x-logging
@@ -648,6 +692,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
@@ -691,6 +736,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
@@ -725,6 +771,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -782,6 +829,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -804,6 +852,7 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_DATABASE_SHARED_TABLES
- _APP_OPTIONS_FORCE_HTTPS
- _APP_MIGRATION_HOST
appwrite-task-maintenance:
entrypoint: maintenance
@@ -821,6 +870,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -865,6 +915,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -903,6 +954,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -934,6 +986,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -965,6 +1018,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -996,6 +1050,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -1024,6 +1079,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -1051,6 +1107,7 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.createAnonymousSession(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.createEmailPasswordSession(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,3 +23,4 @@ account.createEmailToken(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.createEmailVerification(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -8,11 +9,16 @@ Client client = new Client(context)
Account account = new Account(client);
account.createJWT(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
account.createJWT(
0, // duration (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));
Log.d("Appwrite", result.toString());
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -23,3 +24,4 @@ account.createMagicURLToken(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.createMFAAuthenticator(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.createMFAChallenge(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.createMFARecoveryCodes(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -24,3 +25,4 @@ account.createOAuth2Session(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -24,3 +25,4 @@ account.createOAuth2Token(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.createPhoneToken(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.createPhoneVerification(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,3 +23,4 @@ account.createPushTarget(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.createRecovery(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.createSession(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.createVerification(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -23,3 +24,4 @@ account.create(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.deleteIdentity(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.deleteMFAAuthenticator(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.deletePushTarget(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.deleteSession(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.deleteSessions(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.getMFARecoveryCodes(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.getPrefs(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.getSession(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.get(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.listIdentities(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.listLogs(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.listMFAFactors(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.listSessions(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updateEmailVerification(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updateEmail(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updateMagicURLSession(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,3 +23,4 @@ account.updateMFAAuthenticator(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updateMFAChallenge(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -16,3 +17,4 @@ account.updateMFARecoveryCodes(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.updateMFA(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -20,3 +21,4 @@ account.updateName(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updatePassword(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updatePhoneSession(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updatePhoneVerification(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,3 +22,4 @@ account.updatePhone(
})
);
```
@@ -1,3 +1,4 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -24,3 +25,4 @@ account.updatePrefs(
})
);
```

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