Compare commits

...
Author SHA1 Message Date
Jake Barnby 4d275db043 Merge remote-tracking branch 'origin/1.6.x' into feat-test-migrations
# Conflicts:
#	composer.lock
2025-03-19 17:13:07 +13:00
Eldad A. FuxandGitHub 7d86ce76d8 Merge pull request #9536 from appwrite/pla-2687
chore: set min operations to 1 for reads and writes
2025-03-18 16:30:52 +01:00
Chirag Aggarwal aab8bca91e fix: logic 2025-03-18 14:20:25 +00:00
Chirag Aggarwal 70e27748be chore: set min operations to 1 for reads and writes 2025-03-18 12:52:41 +00:00
Jake BarnbyandGitHub 556e00b62b Merge pull request #9526 from appwrite/feat-disable-pdo-persist
Disable PDO persistence since we manage our own pool
2025-03-18 12:19:58 +00:00
Christy JacobandGitHub 370a8e231c Merge pull request #9530 from appwrite/PLA-2662
feat: add pool telemetry
2025-03-18 14:44:52 +05:30
Fabian Gruber aa5ea7f3a1 feat: add pool telemetry 2025-03-18 10:01:06 +01:00
Jake BarnbyandGitHub 8a77eaf2bb Merge pull request #9529 from appwrite/feat-migration-read-db
Migration read from db
2025-03-18 08:01:07 +00:00
Jake Barnby fe308b4f1f Update lib 2025-03-18 20:49:19 +13:00
Jake Barnby e43a3109eb Update migration lib 2025-03-18 20:05:12 +13:00
Jake Barnby d9fbe9658d Merge branch '1.6.x' into feat-migration-read-db 2025-03-18 20:04:23 +13:00
Jake BarnbyandGitHub 1e2f5bcb84 Merge branch '1.6.x' into feat-disable-pdo-persist 2025-03-18 07:02:54 +00:00
Christy JacobandGitHub 088d0998cd Merge pull request #9514 from appwrite/feat-domains-count
Feat: domains count
2025-03-18 11:03:45 +05:30
Jake Barnby a7165e6a32 Disable PDO persistence since we manage our own pool 2025-03-18 16:51:00 +13:00
Jake Barnby 8c855bff10 Merge remote-tracking branch 'origin/feat-bulk-operations' into feat-test-migrations 2025-03-17 21:14:11 +13:00
Jake Barnby bc866dc717 Sync with 1.6.x 2025-03-17 21:10:30 +13:00
Jake Barnby 94f9d0140b Merge branch '1.6.x' into feat-bulk-operations
# Conflicts:
#	app/config/specs/open-api3-1.6.x-client.json
#	app/config/specs/open-api3-1.6.x-console.json
#	app/config/specs/open-api3-1.6.x-server.json
#	app/config/specs/swagger2-1.6.x-client.json
#	app/config/specs/swagger2-1.6.x-console.json
#	app/config/specs/swagger2-1.6.x-server.json
#	app/init.php
#	composer.json
#	composer.lock
2025-03-17 20:44:08 +13:00
Damodar LohaniandGitHub 8fd877810d Merge branch '1.6.x' into feat-domains-count 2025-03-17 12:49:59 +05:45
Jake Barnby 04711d8c02 Add doc 2025-03-17 19:21:31 +13:00
Eldad A. FuxandGitHub 5a5d43f3d0 Merge pull request #9515 from appwrite/feat-env-to-system
Feat env vars to system lib
2025-03-16 17:09:47 +01:00
Eldad A. FuxandGitHub 3e462ded0a Merge pull request #9408 from appwrite/feat-dynamic-regions
Feat dynamic regions
2025-03-16 17:05:04 +01:00
Eldad Fux de96b2511a Merge remote-tracking branch 'origin/1.6.x' into feat-dynamic-regions 2025-03-16 16:31:09 +01:00
Eldad Fux 90899b92e3 Change envs to system lib 2025-03-16 10:36:32 +01:00
Damodar Lohani 4eece99768 Feat: domains count 2025-03-16 02:35:10 +00:00
Eldad A. FuxandGitHub 971a4b8f30 Merge pull request #9511 from appwrite/feat-eldad-cleanups
Cleanups
2025-03-15 19:15:32 +01:00
Eldad Fux 418185b914 fixed format 2025-03-15 14:43:51 +01:00
Eldad Fux 167011ce87 Fixed timelimit 2025-03-15 14:38:01 +01:00
Eldad Fux 3d69a35d5a Fixed tests 2025-03-15 14:32:18 +01:00
Eldad Fux aac90ea8b8 Fixed format 2025-03-15 14:17:47 +01:00
Eldad Fux 56aa503146 Fixed paths 2025-03-15 13:57:12 +01:00
Eldad Fux 875a60fe07 fixed paths 2025-03-15 13:53:00 +01:00
Eldad Fux 3427755695 Merge remote-tracking branch 'origin/1.6.x' into feat-eldad-cleanups 2025-03-15 13:45:31 +01:00
Jake BarnbyandGitHub 68238193d2 Merge pull request #9512 from appwrite/feat-improve-ordering
Improve delete ordering
2025-03-16 01:34:02 +13:00
Eldad Fux 07926534bf Fixed namespace 2025-03-15 13:24:56 +01:00
Eldad Fux a2a0a1405f plurals 2025-03-15 13:23:02 +01:00
Eldad Fux 195edd19ac wip 2025-03-15 13:18:34 +01:00
Eldad Fux 6cf333141c Merge remote-tracking branch 'origin/1.6.x' into feat-eldad-cleanups 2025-03-15 13:18:30 +01:00
Jake Barnby 12dbb1d50d Reapply "Improve deletes queries"
This reverts commit 9dff192508.
2025-03-16 01:02:44 +13:00
Jake Barnby 9dff192508 Revert "Improve deletes queries"
This reverts commit 650627bfb4.
2025-03-16 01:02:41 +13:00
Jake Barnby 650627bfb4 Improve deletes queries 2025-03-16 01:01:13 +13:00
Eldad Fux 4016895b8f Fixed DB test 2025-03-15 12:58:41 +01:00
Eldad Fux 3b7aea22c2 Fixed test 2025-03-15 12:42:34 +01:00
Eldad Fux 50fe0bf98d Code review fix 2025-03-15 12:39:15 +01:00
Eldad Fux 4f0a7de868 Fixes 2025-03-15 12:34:49 +01:00
Eldad Fux 1933e48d9c Cleanups 2025-03-15 12:00:36 +01:00
Steven NguyenandGitHub 90228a5ffb Merge pull request #9294 from appwrite/feat-1.6.1-release-prep
Prepare 1.6.1 release
2025-03-14 16:38:55 -07:00
Steven NguyenandGitHub 063c2be38f Merge pull request #9495 from appwrite/chore-bump-console-5.2.53
Bump console to version 5.2.53
2025-03-14 16:35:22 -07:00
Steven NguyenandGitHub ff9845c9b9 Merge pull request #9496 from appwrite/main
Sync main into 1.6.x
2025-03-14 16:35:01 -07:00
Steven NguyenandGitHub bd5bfc04ef Merge pull request #9466 from appwrite/fix-certificates-in-deletes
Fix certificates in deletes worker
2025-03-14 16:34:21 -07:00
Matej BačoandGitHub c55030387f Merge pull request #9508 from appwrite/feat-rules-improvements
Feat: Add rule attributes
2025-03-14 13:35:05 +01:00
Matej Bačo 5f2512dc4f code quality improvement 2025-03-14 12:07:38 +01:00
Matej Bačo f6fc425c98 Add rule attributes 2025-03-14 09:37:44 +01:00
Jake BarnbyandGitHub 810d650aba Merge pull request #9507 from appwrite/feat-improve-delete-queries
Feat improve delete queries
2025-03-14 18:37:56 +13:00
Jake Barnby 817a3808e5 Add 5 min timeout for workers and tasks DBs 2025-03-14 16:40:57 +13:00
Jake Barnby d96910138f Update lock 2025-03-14 15:40:05 +13:00
Jake Barnby b8673ac07f Merge remote-tracking branch 'origin/1.6.x' into feat-improve-delete-queries
# Conflicts:
#	composer.lock
2025-03-14 15:34:18 +13:00
Jake Barnby 246c8ce939 Add order on inequality queries 2025-03-14 15:29:52 +13:00
Jake Barnby 49d89a4206 Force clear timeout to ensure it's not set 2025-03-14 15:29:23 +13:00
Christy JacobandGitHub 2c803b262c Merge pull request #9505 from appwrite/PLA-2640
update queue to 0.9.*
2025-03-13 18:51:41 +05:30
Fabian Gruber fcf15689e8 update queue to 0.9.* 2025-03-13 14:02:32 +01:00
Christy JacobandGitHub aef38aa114 Merge pull request #9414 from appwrite/bump-sdk
chore: bump sdk
2025-03-13 17:29:56 +05:30
Chirag AggarwalandGitHub d81f2f8b5c Merge branch '1.6.x' into bump-sdk 2025-03-13 11:01:14 +05:30
Chirag Aggarwal e119bf6777 chore: update changelogs 2025-03-13 05:30:09 +00:00
Steven Nguyen 7402145c9d Bump console to version 5.2.53 2025-03-12 15:22:43 -07:00
Chirag Aggarwal 74f4ebf90e chore: update sdk generator 2025-03-12 10:26:01 +00:00
Chirag Aggarwal e3f59c696d bump: python version 2025-03-11 05:43:30 +00:00
Chirag Aggarwal 107a185953 Merge branch '1.6.x' into bump-sdk 2025-03-11 05:30:47 +00:00
Steven Nguyen f6b5deda6d fix: add missing _APP_EMAIL_CERTIFICATES env var to deletes worker
The deletes worker uses the certificates resource to delete
certificates, but the certificates resource requires the
_APP_EMAIL_CERTIFICATES env var to be set or it will throw an exception
and not process any delete jobs. This commit adds the missing env var
to the deletes worker.

Also add _APP_SYSTEM_SECURITY_EMAIL_ADDRESS as a fallback.
2025-03-07 12:46:14 -08:00
Chirag Aggarwal d9fc6b70ad chore: bump react native 2025-03-07 13:18:20 +00:00
Chirag Aggarwal a0605b14cb chore: regen specs and update vers 2025-03-07 04:14:45 +00:00
Chirag Aggarwal 6c652ba3ae Merge branch '1.6.x' into bump-sdk 2025-03-06 17:04:05 +00:00
Jake Barnby 2f43a3566d Remove bulk update permissions param 2025-03-05 16:42:09 +13:00
Jake Barnby 0f0ea5e96c Update deps 2025-03-05 15:18:14 +13:00
Jake Barnby 99377e26ee Reset fixes 2025-03-05 15:09:06 +13:00
Jake Barnby d809ab1ff3 Merge branch 'feat-bulk-operations' of github.com:appwrite/appwrite into feat-bulk-operations
# Conflicts:
#	app/controllers/api/databases.php
#	app/controllers/shared/api.php
#	composer.json
#	composer.lock
2025-03-05 12:47:47 +13:00
Jake Barnby a5c4fe0a2d Merge remote-tracking branch 'origin/1.6.x' into feat-bulk-operations
# Conflicts:
#	app/config/specs/open-api3-latest-client.json
#	app/config/specs/open-api3-latest-console.json
#	app/config/specs/open-api3-latest-server.json
#	app/config/specs/swagger2-latest-client.json
#	app/config/specs/swagger2-latest-console.json
#	app/config/specs/swagger2-latest-server.json
#	app/controllers/api/databases.php
#	app/controllers/shared/api.php
#	composer.json
#	composer.lock
#	docs/references/databases/create-document.md
2025-03-05 12:44:39 +13:00
Chirag Aggarwal 6ad34a7bf7 chore: sdk versions and corresponding docs 2025-02-27 05:05:31 +00:00
Chirag Aggarwal 57c7ebb53d chore: bump sdk 2025-02-26 17:51:00 +00:00
Eldad Fux ad56782b49 Fixed tests 2025-02-26 10:19:51 +01:00
Eldad Fux ce5a04a37d Merge remote-tracking branch 'origin/1.6.x' into feat-dynamic-regions 2025-02-26 07:52:40 +01:00
Eldad Fux e8fb2f8ae1 Changed config format 2025-02-25 20:25:54 +01:00
Eldad Fux 697c30f559 Init 2025-02-25 09:31:24 +01:00
Steven NguyenandGitHub 34d587d7c5 Merge pull request #9122 from gurjeetsinghvirdee/152-sdk-cli-command-update
Fix outdated CLI commands in documentation
2025-02-21 13:59:50 -08:00
Bradley Schofield c560524a42 Add more benchmarks, update DB Lib 2025-02-13 16:52:53 +09:00
Bradley Schofield 0ccaf9adff Add more benchmarks 2025-02-11 15:57:27 +09:00
d490879615 Update app/controllers/api/databases.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-02-11 09:24:35 +09:00
709f616f7f Update app/controllers/api/databases.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-02-11 09:24:26 +09:00
a8a5b4d00c Update docs/references/databases/update-documents.md
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-02-11 09:24:18 +09:00
Bradley Schofield a27ba015e9 Merge branch '1.6.x' into feat-bulk-operations
# Conflicts:
#	app/controllers/api/databases.php
#	app/controllers/shared/api.php
#	composer.json
#	composer.lock
2025-02-11 09:23:49 +09:00
Jake Barnby 386c6076b2 Merge branch '1.6.x' into feat-bulk-operations
# Conflicts:
#	composer.json
#	composer.lock
2025-02-05 01:52:35 +13:00
Steven NguyenandGitHub 0547dbc454 fix: use github var instead of secret for DOCKERHUB_USERNAME
DOCKERHUB_USERNAME was moved to be a variable since it doesn't need to
be secret.
2025-02-03 22:34:08 +00:00
Gurjeet Singh VirdeeandGitHub a3324b7453 Merge branch 'main' into 152-sdk-cli-command-update 2025-02-01 03:43:11 +05:30
Steven NguyenandGitHub df017bccb2 chore: bump appwrite version to 1.6.1 2025-01-31 22:00:32 +00:00
Bradley Schofield bd20ea0300 Fix additional methods with multiple responses 2025-01-30 14:19:20 +09:00
Eldad A. FuxandGitHub c4ab6f5aff Merge pull request #9285 from appwrite/LauraDuRy-patch-1
replaced init for cloud link
2025-01-29 12:06:46 +01:00
Laura Du RyandGitHub 891c1c4093 Update README.md 2025-01-29 10:04:15 +01:00
Laura Du RyandGitHub 507c8bd3ec Update README.md 2025-01-29 10:02:40 +01:00
Laura Du RyandGitHub 2de4e71662 Update README.md 2025-01-29 09:58:04 +01:00
Bradley Schofield 40f8a4cb02 Merge branch '1.6.x' into feat-bulk-operations 2025-01-27 09:36:15 +09:00
Gurjeet Singh VirdeeandGitHub 8508e6009b refactor: cleaned up syntax by removing unnecessary '=' 2025-01-24 19:13:56 +05:30
Bradley Schofield 5dadd8caea Fix required/whitelist system and descriptions 2025-01-24 16:19:43 +09:00
Bradley Schofield a6f459ecca Cleanup a few leftover from development 2025-01-24 14:26:17 +09:00
Bradley Schofield 2647379557 Fix missing dependency 2025-01-23 13:24:56 +09:00
Bradley Schofield 8fb7a2db32 Merge branch '1.6.x' into feat-bulk-operations
# Conflicts:
#	app/config/specs/open-api3-latest-client.json
#	app/config/specs/open-api3-latest-console.json
#	app/config/specs/open-api3-latest-server.json
#	app/config/specs/swagger2-latest-client.json
#	app/config/specs/swagger2-latest-console.json
#	app/config/specs/swagger2-latest-server.json
#	app/controllers/api/databases.php
#	app/controllers/api/functions.php
#	app/controllers/api/migrations.php
#	app/controllers/api/projects.php
#	app/controllers/api/proxy.php
#	app/controllers/api/storage.php
#	app/controllers/api/users.php
#	app/controllers/api/vcs.php
#	composer.json
#	composer.lock
#	docs/references/account/create-push-target.md
#	docs/references/account/delete-push-target.md
#	docs/references/account/update-push-target.md
#	src/Appwrite/SDK/Method.php
#	src/Appwrite/Specification/Format/OpenAPI3.php
#	src/Appwrite/Specification/Format/Swagger2.php
2025-01-23 11:19:20 +09:00
Jake BarnbyandGitHub 39c70b3539 Merge pull request #9108 from appwrite/feat-new-sdk-label
Feat new sdk label
2025-01-16 23:19:29 +13:00
Bradley Schofield e0101def27 Fix tests 2025-01-16 17:47:36 +09:00
Bradley Schofield 469732f482 Address Comments 2025-01-16 14:34:53 +09:00
Bradley Schofield 4c22088146 Address GraphQL 2025-01-16 11:30:50 +09:00
Bradley Schofield 9d04c61cbe Attempt to fix tests 2025-01-16 11:07:09 +09:00
Bradley Schofield 48bf8001ae Address Comments 2025-01-15 16:17:35 +09:00
Bradley Schofield 1691910023 Run Linter 2025-01-15 12:56:46 +09:00
Bradley Schofield d6474a85e2 Address Comments 2025-01-15 12:54:49 +09:00
Bradley Schofield 8d7712a669 Remove debug flag 2025-01-15 09:59:16 +09:00
Bradley Schofield 396602d887 Address Comments 2025-01-15 09:58:57 +09:00
Bradley Schofield bc85d04bbc Run Linter 2025-01-14 18:10:27 +09:00
Bradley Schofield ec44b1f272 Make all delete routes return none 2025-01-14 18:09:37 +09:00
Bradley Schofield efd8dc16b3 Address Comments 2025-01-14 17:52:31 +09:00
Bradley Schofield d54d0ea7b3 Address error validation comment 2025-01-13 09:35:05 +09:00
Bradley Schofield 8f2cca5b0f Fix Response Models 2025-01-10 16:53:09 +09:00
Bradley Schofield a1c70a6956 Run Formatter, Fix GraphQL 2025-01-10 14:23:04 +09:00
Bradley Schofield 87de19b610 Address Comments 2025-01-10 02:07:56 +09:00
Gurjeet Singh VirdeeandGitHub a39341ada8 fix: add missing single quote 2025-01-08 14:48:54 +05:30
Gurjeet Singh VirdeeandGitHub c451074278 restored the permissions term 2025-01-06 01:29:06 +05:30
Bradley Schofield 0d6095ba18 Add improved error handling for SDK Class 2025-01-03 14:16:26 +00:00
Bradley Schofield 6a782ce373 Implement SDKResponse class 2024-12-30 11:32:40 +00:00
Gurjeet Singh VirdeeandGitHub cce6222184 docs: update CLI commands in documentation 2024-12-20 22:24:39 +05:30
Bradley Schofield 624c91816b Begin work on SDKResponse refactor 2024-12-20 12:06:09 +09:00
Bradley Schofield 64a957859e Run Linter 2024-12-17 10:39:06 +09:00
Bradley Schofield fa053e493a Update specs 2024-12-17 10:33:08 +09:00
Bradley Schofield 1361dbdec5 Remove get version API from SDKs 2024-12-17 10:33:02 +09:00
Bradley Schofield 8ae462c317 Fix JSON Encode for multiplex objects 2024-12-17 10:31:26 +09:00
Bradley Schofield 2997a11274 Add SDK Multiplexing 2024-12-17 10:27:00 +09:00
Bradley Schofield 67a72edebf Run Linter 2024-12-16 15:03:57 +09:00
Bradley Schofield b4c77fbd5f Get Initial Draft Working 2024-12-16 14:59:01 +09:00
Bradley Schofield f7e69624c6 Address comments 2024-12-11 10:19:10 +09:00
Bradley Schofield e9c2ebbcf5 Implement SDK Multiplexing for Create Document/s 2024-12-10 12:31:26 +09:00
Bradley Schofield 879ddb3074 Implement SDK Method Multiplexing 2024-12-09 14:21:56 +09:00
Jake BarnbyandGitHub 8c1f6aabb7 Merge pull request #8968 from appwrite/feat-bulk-create
Feat: Bulk Create Documents
2024-12-04 19:03:31 +13:00
Bradley Schofield f707be6ed3 Add extra tests 2024-12-04 09:13:08 +09:00
Bradley Schofield bcb4ffa404 Fix Tests 2024-12-03 17:39:25 +09:00
Bradley Schofield c0d4b58eea Merge branch 'feat-bulk-operations' into feat-bulk-create
# Conflicts:
#	tests/e2e/Services/Databases/DatabasesCustomClientTest.php
2024-12-03 17:06:35 +09:00
Bradley Schofield e2ab6a05ab Fix permissions and accompanying tests 2024-12-03 16:40:03 +09:00
Jake BarnbyandGitHub 6bbb522a87 Merge pull request #8955 from appwrite/feat-bulk-updates
Feat bulk updates
2024-12-03 19:35:45 +13:00
Bradley Schofield 7f2d3beb31 Run Linter 2024-12-03 15:19:58 +09:00
Bradley Schofield 3d4917727b Address Comments 2024-12-03 15:14:07 +09:00
Bradley SchofieldandGitHub 41ead533b8 Linter 2024-12-03 05:03:51 +00:00
Bradley SchofieldandGitHub c47f65d27c Merge remote-tracking branch 'origin/feat-bulk-operations' into feat-bulk-updates 2024-12-03 05:03:02 +00:00
Bradley SchofieldandGitHub 116ba6bc7b Fix Problems with tests and params 2024-12-03 04:38:59 +00:00
Jake BarnbyandGitHub adc94cf1b4 Merge pull request #8942 from appwrite/feat-bulk-deletes
Feat bulk deletes
2024-12-03 17:36:06 +13:00
Jake BarnbyandGitHub 257733aec0 Update app/controllers/api/databases.php 2024-12-03 17:35:46 +13:00
Bradley Schofield fbbf846eb9 Run Linter 2024-12-02 19:59:48 +09:00
Bradley Schofield fbf985ea90 Add event 2024-12-02 19:57:21 +09:00
Bradley Schofield f061f724e0 Fix params 2024-12-02 19:06:34 +09:00
Bradley Schofield 125585831b Remove bulk deletes realtime test 2024-12-02 18:16:04 +09:00
Bradley Schofield 3c418c1607 Address params and events 2024-12-02 18:08:10 +09:00
Bradley Schofield 8f8c008e8b Fix params and usage 2024-12-02 18:06:04 +09:00
Bradley Schofield 84ba7febe7 Run Linter 2024-12-02 16:45:03 +09:00
Bradley Schofield 7b79642f4b Run Linter 2024-12-02 16:44:15 +09:00
Bradley Schofield 949347764a Add new database events 2024-12-02 16:43:19 +09:00
Bradley Schofield 32c4debffc Remove realtime/events from bulk operations and get usage events working 2024-12-02 16:40:19 +09:00
Bradley Schofield 2850879428 Remove Events for bulk updates 2024-12-02 16:07:32 +09:00
Bradley Schofield 1c0c8f0eb6 Run Formatter 2024-12-02 13:15:41 +09:00
Bradley Schofield 00e3fe12f4 Revert "Fix wrong params on feedback, disable debugging in compose file"
This reverts commit 5c895579ed.
2024-11-28 17:25:38 +09:00
Bradley Schofield 5c895579ed Fix wrong params on feedback, disable debugging in compose file 2024-11-28 17:24:53 +09:00
Bradley Schofield 104a9b6074 Remove realtime support for bulk operations 2024-11-28 16:29:22 +09:00
Steven NguyenandGitHub 6e09bcf6c6 Bump appwrite version to 1.6.1-RC1 2024-11-27 18:10:28 +00:00
Bradley Schofield 2b08db9a38 Address Comments 2024-11-25 16:22:02 +09:00
Bradley Schofield 9d652d9e79 Remove double realtime 2024-11-25 16:08:50 +09:00
Bradley Schofield e1fffa775e Fix Tests 2024-11-25 15:23:22 +09:00
Bradley Schofield 2e60698ec1 Fix double event execution on document creation 2024-11-25 15:21:56 +09:00
Bradley Schofield 90521c6bd7 Update database library version 2024-11-25 15:02:36 +09:00
Bradley Schofield 558f7f06b4 Fix Missing Model 2024-11-25 14:53:35 +09:00
Bradley Schofield 19194dabc0 Remove Bulk Operation Model 2024-11-25 14:53:01 +09:00
Bradley Schofield e1230f7465 Address Comments 2024-11-25 14:45:21 +09:00
Bradley Schofield def25a8c9b Merge branch 'feat-bulk-deletes' of https://github.com/appwrite/appwrite into feat-bulk-deletes 2024-11-25 14:34:29 +09:00
Bradley Schofield 675227d25a Address Comments 2024-11-25 14:34:23 +09:00
4e5ad33d73 Update app/controllers/api/databases.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2024-11-25 14:29:51 +09:00
Bradley Schofield 54260b5a9a Use new API Spec and get realtime/events working 2024-11-22 11:51:17 +09:00
Bradley Schofield 67b95c4b39 Update API Signature to DB Lib and get realtime/events working 2024-11-22 11:07:05 +09:00
Bradley Schofield 90c4bad826 Run Linter 2024-11-22 10:44:19 +09:00
Bradley Schofield 791f0dda79 Update to use new DB Lib and get realtime working 2024-11-22 10:43:45 +09:00
Bradley Schofield 79c043ec1f Fix Tests 2024-11-19 11:47:23 +09:00
Bradley Schofield 755e86020f Continue work on getting events working 2024-11-19 10:09:55 +09:00
Bradley Schofield 96e69b55e1 Add tests that are currently failing because of realtime and attempt to fix them 2024-11-15 12:00:11 +09:00
Bradley Schofield c210b2b874 Address Comments 2024-11-14 17:16:39 +09:00
Bradley Schofield c0ef832784 Try and fix events 2024-11-14 17:15:28 +09:00
Bradley Schofield bb7234fa98 Update database version 2024-11-13 13:45:16 +09:00
Bradley Schofield dd6bd745df Address Comments, fix permission bug 2024-11-13 13:41:18 +09:00
Bradley Schofield a8f3b63ded Add Tests for Relationships and Permissions, Update error messages to be a little clearer 2024-11-13 10:00:40 +09:00
Bradley Schofield aa42608528 Regen Specs 2024-11-13 09:22:47 +09:00
Bradley Schofield 172d25d67c Address Comments 2024-11-12 15:43:36 +09:00
Bradley Schofield 92c93357cf Address Jake's Comments 2024-11-12 11:41:08 +09:00
Bradley Schofield 6faad29907 Initial Version 2024-11-12 10:20:46 +09:00
Bradley Schofield 07738bdcf9 Update Specs 2024-11-11 15:29:59 +09:00
Bradley Schofield c6ca830801 Merge branch 'feat-bulk-operations' into feat-bulk-updates 2024-11-11 15:27:37 +09:00
Bradley Schofield 44234da995 Implement Bulk Updates API 2024-11-11 15:27:26 +09:00
Bradley SchofieldandGitHub 22dfcb78c4 Merge branch 'feat-bulk-operations' into feat-bulk-deletes 2024-11-11 03:59:23 +00:00
Bradley SchofieldandGitHub 60da24e521 Run Linter 2024-11-11 03:58:24 +00:00
Bradley Schofield f703a0db18 Merge branch 'feat-bulk-operations' into feat-bulk-deletes
# Conflicts:
#	app/config/specs/open-api3-latest-client.json
#	app/config/specs/open-api3-latest-console.json
#	app/config/specs/open-api3-latest-server.json
#	app/config/specs/swagger2-latest-client.json
#	app/config/specs/swagger2-latest-console.json
#	app/config/specs/swagger2-latest-server.json
#	src/Appwrite/Utopia/Response/Model/BulkOperation.php
2024-11-11 12:41:24 +09:00
Bradley Schofield 2a8ab8d610 Add base bulk operation models and envvars 2024-11-11 12:36:58 +09:00
Bradley Schofield 5bf20289a5 Address Comments 2024-11-08 16:17:29 +09:00
8a601ee058 Update src/Appwrite/Utopia/Response/Model/BulkOperation.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2024-11-07 14:41:14 +09:00
Bradley Schofield 95861d068d Fix Tests 2024-11-07 13:14:15 +09:00
Bradley Schofield 313adc7023 Merge branch '1.6.x' into feat-bulk-deletes
# Conflicts:
#	composer.json
#	composer.lock
2024-11-07 12:37:08 +09:00
Bradley Schofield bb40455cb7 Add documentation, update specs 2024-11-07 12:34:18 +09:00
Bradley Schofield 86053e9028 Implement Bulk Deletes API 2024-11-07 12:30:17 +09:00
61 changed files with 10181 additions and 4337 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
+3 -5
View File
@@ -1,4 +1,4 @@
> Appwrite Init has concluded! You can check out all the latest announcements [on our Init website](https://appwrite.io/init) :rocket:
> [Get started with Appwrite](https://apwr.dev/appcloud)
<br />
<p align="center">
@@ -24,11 +24,9 @@
English | [简体中文](README-CN.md)
[**Announcing Appwrite Cloud Public Beta! Sign up today!**](https://cloud.appwrite.io)
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<nobr> microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, and [more services](https://appwrite.io/docs).
Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and [more services](https://appwrite.io/docs).
<p align="center">
<br />
@@ -52,7 +50,7 @@ Table of Contents:
- [Upgrade from an Older Version](#upgrade-from-an-older-version)
- [One-Click Setups](#one-click-setups)
- [Getting Started](#getting-started)
- [Services](#services)
- [Products](#products)
- [SDKs](#sdks)
- [Client](#client)
- [Server](#server)
+3 -4
View File
@@ -25,7 +25,7 @@ use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\System\System;
// overwriting runtimes to be architectur agnostic for CLI
// Overwriting runtimes to be architecture agnostic for CLI
Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false));
// require controllers after overwriting runtimes
@@ -43,8 +43,7 @@ CLI::setResource('cache', function ($pools) {
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
->getResource();
}
return new Cache(new Sharding($adapters));
@@ -187,7 +186,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
+37 -1
View File
@@ -1066,7 +1066,29 @@ return [
'default' => null,
'array' => false,
'filters' => [],
]
],
[
'$id' => ID::custom('owner'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'signed' => true,
'required' => false,
'default' => '', // "Appwrite" or empty string
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('region'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
@@ -1111,6 +1133,20 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_owner'),
'type' => Database::INDEX_KEY,
'attributes' => ['owner'],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_region'),
'type' => Database::INDEX_KEY,
'attributes' => ['region'],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
],
],
+1
View File
@@ -27,6 +27,7 @@ $console = [
'hostname' => 'localhost',
], // Current host is added on app init
],
'region' => 'fra',
'legalName' => '',
'legalCountry' => '',
'legalState' => '',
+42 -7
View File
@@ -1,11 +1,46 @@
<?php
/**
* Continent codes with names and approximate central coordinates
*
* Coordinates represent approximate geographical centers of each continent
* Note: These are simplified centroids and may not represent the exact geographical center
*/
return [
'AF',
'AN',
'AS',
'EU',
'NA',
'OC',
'SA',
'AF' => [
'name' => 'Africa',
'latitude' => 8.7832,
'longitude' => 34.5085
],
'AN' => [
'name' => 'Antarctica',
'latitude' => -82.8628,
'longitude' => 135.0000
],
'AS' => [
'name' => 'Asia',
'latitude' => 34.0479,
'longitude' => 100.6197
],
'EU' => [
'name' => 'Europe',
'latitude' => 54.5260,
'longitude' => 15.2551
],
'NA' => [
'name' => 'North America',
'latitude' => 54.5260,
'longitude' => -105.2551
],
'OC' => [
'name' => 'Oceania',
'latitude' => -22.7359,
'longitude' => 140.0188
],
'SA' => [
'name' => 'South America',
'latitude' => -8.7832,
'longitude' => -55.4915
],
];
+199 -198
View File
@@ -1,209 +1,210 @@
<?php
/**
* ISO 3166 standard country codes
* ISO 3166 standard country codes with country names and coordinates
* https://www.iso.org/iso-3166-country-codes.html
*
* Source:
* https://www.iso.org/obp/ui/#search/code/
* Coordinates source: Natural Earth Data (approximate country centroids)
*/
return [
'AF',
'AO',
'AL',
'AD',
'AE',
'AR',
'AM',
'AG',
'AU',
'AT',
'AZ',
'BI',
'BE',
'BJ',
'BF',
'BD',
'BG',
'BH',
'BS',
'BA',
'BY',
'BZ',
'BO',
'BR',
'BB',
'BN',
'BT',
'BW',
'CF',
'CA',
'CH',
'CL',
'CN',
'CI',
'CM',
'CD',
'CG',
'CO',
'KM',
'CV',
'CR',
'CU',
'CY',
'CZ',
'DE',
'DJ',
'DM',
'DK',
'DO',
'DZ',
'EC',
'EG',
'ER',
'ES',
'EE',
'ET',
'FI',
'FJ',
'FR',
'FM',
'GA',
'GB',
'GE',
'GH',
'GN',
'GM',
'GW',
'GQ',
'GR',
'GD',
'GT',
'GY',
'HK',
'HN',
'HR',
'HT',
'HU',
'ID',
'IN',
'IE',
'IR',
'IQ',
'IS',
'IL',
'IT',
'JM',
'JO',
'JP',
'KZ',
'KE',
'KG',
'KH',
'KI',
'KN',
'KR',
'KW',
'LA',
'LB',
'LR',
'LY',
'LC',
'LI',
'LK',
'LS',
'LT',
'LU',
'LV',
'MA',
'MC',
'MD',
'MG',
'MV',
'MX',
'MH',
'MK',
'ML',
'MT',
'MM',
'ME',
'MN',
'MZ',
'MR',
'MU',
'MW',
'MY',
'NA',
'NE',
'NG',
'NI',
'NL',
'NO',
'NP',
'NR',
'NZ',
'OM',
'PK',
'PS',
'PA',
'PE',
'PH',
'PW',
'PG',
'PL',
'KP',
'PT',
'PY',
'QA',
'RO',
'RU',
'RW',
'SA',
'SD',
'SN',
'SG',
'SB',
'SL',
'SV',
'SM',
'SO',
'RS',
'SS',
'ST',
'SR',
'SK',
'SI',
'SE',
'SZ',
'SC',
'SY',
'TD',
'TG',
'TH',
'TJ',
'TM',
'TL',
'TO',
'TT',
'TN',
'TR',
'TV',
'TZ',
'TW',
'UG',
'UA',
'UY',
'US',
'UZ',
'VA',
'VC',
'VE',
'VN',
'VU',
'WS',
'YE',
'ZA',
'ZM',
'ZW',
'AF' => ['name' => 'Afghanistan', 'latitude' => 33.0, 'longitude' => 66.0],
'AO' => ['name' => 'Angola', 'latitude' => -12.5, 'longitude' => 18.5],
'AL' => ['name' => 'Albania', 'latitude' => 41.0, 'longitude' => 20.0],
'AD' => ['name' => 'Andorra', 'latitude' => 42.5, 'longitude' => 1.6],
'AE' => ['name' => 'United Arab Emirates', 'latitude' => 24.0, 'longitude' => 54.0],
'AR' => ['name' => 'Argentina', 'latitude' => -34.0, 'longitude' => -64.0],
'AM' => ['name' => 'Armenia', 'latitude' => 40.0, 'longitude' => 45.0],
'AG' => ['name' => 'Antigua and Barbuda', 'latitude' => 17.05, 'longitude' => -61.8],
'AU' => ['name' => 'Australia', 'latitude' => -25.0, 'longitude' => 135.0],
'AT' => ['name' => 'Austria', 'latitude' => 47.3, 'longitude' => 13.3],
'AZ' => ['name' => 'Azerbaijan', 'latitude' => 40.5, 'longitude' => 47.5],
'BI' => ['name' => 'Burundi', 'latitude' => -3.5, 'longitude' => 30.0],
'BE' => ['name' => 'Belgium', 'latitude' => 50.8, 'longitude' => 4.0],
'BJ' => ['name' => 'Benin', 'latitude' => 9.5, 'longitude' => 2.25],
'BF' => ['name' => 'Burkina Faso', 'latitude' => 13.0, 'longitude' => -2.0],
'BD' => ['name' => 'Bangladesh', 'latitude' => 24.0, 'longitude' => 90.0],
'BG' => ['name' => 'Bulgaria', 'latitude' => 43.0, 'longitude' => 25.0],
'BH' => ['name' => 'Bahrain', 'latitude' => 26.0, 'longitude' => 50.5],
'BS' => ['name' => 'Bahamas', 'latitude' => 24.25, 'longitude' => -76.0],
'BA' => ['name' => 'Bosnia and Herzegovina', 'latitude' => 44.0, 'longitude' => 18.0],
'BY' => ['name' => 'Belarus', 'latitude' => 53.0, 'longitude' => 28.0],
'BZ' => ['name' => 'Belize', 'latitude' => 17.25, 'longitude' => -88.75],
'BO' => ['name' => 'Bolivia', 'latitude' => -17.0, 'longitude' => -65.0],
'BR' => ['name' => 'Brazil', 'latitude' => -10.0, 'longitude' => -55.0],
'BB' => ['name' => 'Barbados', 'latitude' => 13.17, 'longitude' => -59.53],
'BN' => ['name' => 'Brunei', 'latitude' => 4.5, 'longitude' => 114.67],
'BT' => ['name' => 'Bhutan', 'latitude' => 27.5, 'longitude' => 90.5],
'BW' => ['name' => 'Botswana', 'latitude' => -22.0, 'longitude' => 24.0],
'CF' => ['name' => 'Central African Republic', 'latitude' => 7.0, 'longitude' => 21.0],
'CA' => ['name' => 'Canada', 'latitude' => 60.0, 'longitude' => -95.0],
'CH' => ['name' => 'Switzerland', 'latitude' => 47.0, 'longitude' => 8.0],
'CL' => ['name' => 'Chile', 'latitude' => -30.0, 'longitude' => -71.0],
'CN' => ['name' => 'China', 'latitude' => 35.0, 'longitude' => 105.0],
'CI' => ['name' => 'Côte d\'Ivoire', 'latitude' => 8.0, 'longitude' => -5.0],
'CM' => ['name' => 'Cameroon', 'latitude' => 6.0, 'longitude' => 12.0],
'CD' => ['name' => 'Democratic Republic of the Congo', 'latitude' => -2.5, 'longitude' => 23.5],
'CG' => ['name' => 'Republic of the Congo', 'latitude' => -1.0, 'longitude' => 15.0],
'CO' => ['name' => 'Colombia', 'latitude' => 4.0, 'longitude' => -72.0],
'KM' => ['name' => 'Comoros', 'latitude' => -12.17, 'longitude' => 44.25],
'CV' => ['name' => 'Cape Verde', 'latitude' => 16.0, 'longitude' => -24.0],
'CR' => ['name' => 'Costa Rica', 'latitude' => 10.0, 'longitude' => -84.0],
'CU' => ['name' => 'Cuba', 'latitude' => 21.5, 'longitude' => -80.0],
'CY' => ['name' => 'Cyprus', 'latitude' => 35.0, 'longitude' => 33.0],
'CZ' => ['name' => 'Czech Republic', 'latitude' => 49.75, 'longitude' => 15.5],
'DE' => ['name' => 'Germany', 'latitude' => 51.0, 'longitude' => 9.0],
'DJ' => ['name' => 'Djibouti', 'latitude' => 11.5, 'longitude' => 43.0],
'DM' => ['name' => 'Dominica', 'latitude' => 15.42, 'longitude' => -61.33],
'DK' => ['name' => 'Denmark', 'latitude' => 56.0, 'longitude' => 10.0],
'DO' => ['name' => 'Dominican Republic', 'latitude' => 19.0, 'longitude' => -70.67],
'DZ' => ['name' => 'Algeria', 'latitude' => 28.0, 'longitude' => 3.0],
'EC' => ['name' => 'Ecuador', 'latitude' => -2.0, 'longitude' => -77.5],
'EG' => ['name' => 'Egypt', 'latitude' => 27.0, 'longitude' => 30.0],
'ER' => ['name' => 'Eritrea', 'latitude' => 15.0, 'longitude' => 39.0],
'ES' => ['name' => 'Spain', 'latitude' => 40.0, 'longitude' => -4.0],
'EE' => ['name' => 'Estonia', 'latitude' => 59.0, 'longitude' => 26.0],
'ET' => ['name' => 'Ethiopia', 'latitude' => 8.0, 'longitude' => 38.0],
'FI' => ['name' => 'Finland', 'latitude' => 64.0, 'longitude' => 26.0],
'FJ' => ['name' => 'Fiji', 'latitude' => -18.0, 'longitude' => 175.0],
'FR' => ['name' => 'France', 'latitude' => 46.0, 'longitude' => 2.0],
'FM' => ['name' => 'Micronesia', 'latitude' => 6.92, 'longitude' => 158.25],
'GA' => ['name' => 'Gabon', 'latitude' => -1.0, 'longitude' => 11.75],
'GB' => ['name' => 'United Kingdom', 'latitude' => 54.0, 'longitude' => -2.0],
'GE' => ['name' => 'Georgia', 'latitude' => 42.0, 'longitude' => 43.5],
'GH' => ['name' => 'Ghana', 'latitude' => 8.0, 'longitude' => -2.0],
'GN' => ['name' => 'Guinea', 'latitude' => 11.0, 'longitude' => -10.0],
'GM' => ['name' => 'Gambia', 'latitude' => 13.47, 'longitude' => -16.57],
'GW' => ['name' => 'Guinea-Bissau', 'latitude' => 12.0, 'longitude' => -15.0],
'GQ' => ['name' => 'Equatorial Guinea', 'latitude' => 2.0, 'longitude' => 10.0],
'GR' => ['name' => 'Greece', 'latitude' => 39.0, 'longitude' => 22.0],
'GD' => ['name' => 'Grenada', 'latitude' => 12.12, 'longitude' => -61.67],
'GT' => ['name' => 'Guatemala', 'latitude' => 15.5, 'longitude' => -90.25],
'GY' => ['name' => 'Guyana', 'latitude' => 5.0, 'longitude' => -59.0],
'HK' => ['name' => 'Hong Kong', 'latitude' => 22.25, 'longitude' => 114.17],
'HN' => ['name' => 'Honduras', 'latitude' => 15.0, 'longitude' => -86.5],
'HR' => ['name' => 'Croatia', 'latitude' => 45.17, 'longitude' => 15.5],
'HT' => ['name' => 'Haiti', 'latitude' => 19.0, 'longitude' => -72.42],
'HU' => ['name' => 'Hungary', 'latitude' => 47.0, 'longitude' => 20.0],
'ID' => ['name' => 'Indonesia', 'latitude' => -5.0, 'longitude' => 120.0],
'IN' => ['name' => 'India', 'latitude' => 20.0, 'longitude' => 77.0],
'IE' => ['name' => 'Ireland', 'latitude' => 53.0, 'longitude' => -8.0],
'IR' => ['name' => 'Iran', 'latitude' => 32.0, 'longitude' => 53.0],
'IQ' => ['name' => 'Iraq', 'latitude' => 33.0, 'longitude' => 44.0],
'IS' => ['name' => 'Iceland', 'latitude' => 65.0, 'longitude' => -18.0],
'IL' => ['name' => 'Israel', 'latitude' => 31.5, 'longitude' => 34.75],
'IT' => ['name' => 'Italy', 'latitude' => 42.83, 'longitude' => 12.83],
'JM' => ['name' => 'Jamaica', 'latitude' => 18.25, 'longitude' => -77.5],
'JO' => ['name' => 'Jordan', 'latitude' => 31.0, 'longitude' => 36.0],
'JP' => ['name' => 'Japan', 'latitude' => 36.0, 'longitude' => 138.0],
'KZ' => ['name' => 'Kazakhstan', 'latitude' => 48.0, 'longitude' => 68.0],
'KE' => ['name' => 'Kenya', 'latitude' => 1.0, 'longitude' => 38.0],
'KG' => ['name' => 'Kyrgyzstan', 'latitude' => 41.0, 'longitude' => 75.0],
'KH' => ['name' => 'Cambodia', 'latitude' => 13.0, 'longitude' => 105.0],
'KI' => ['name' => 'Kiribati', 'latitude' => 1.42, 'longitude' => 173.0],
'KN' => ['name' => 'Saint Kitts and Nevis', 'latitude' => 17.33, 'longitude' => -62.75],
'KR' => ['name' => 'South Korea', 'latitude' => 37.0, 'longitude' => 127.5],
'KW' => ['name' => 'Kuwait', 'latitude' => 29.34, 'longitude' => 47.66],
'LA' => ['name' => 'Laos', 'latitude' => 18.0, 'longitude' => 105.0],
'LB' => ['name' => 'Lebanon', 'latitude' => 33.83, 'longitude' => 35.83],
'LR' => ['name' => 'Liberia', 'latitude' => 6.5, 'longitude' => -9.5],
'LY' => ['name' => 'Libya', 'latitude' => 25.0, 'longitude' => 17.0],
'LC' => ['name' => 'Saint Lucia', 'latitude' => 13.88, 'longitude' => -61.13],
'LI' => ['name' => 'Liechtenstein', 'latitude' => 47.17, 'longitude' => 9.53],
'LK' => ['name' => 'Sri Lanka', 'latitude' => 7.0, 'longitude' => 81.0],
'LS' => ['name' => 'Lesotho', 'latitude' => -29.5, 'longitude' => 28.5],
'LT' => ['name' => 'Lithuania', 'latitude' => 56.0, 'longitude' => 24.0],
'LU' => ['name' => 'Luxembourg', 'latitude' => 49.75, 'longitude' => 6.17],
'LV' => ['name' => 'latitudevia', 'latitude' => 57.0, 'longitude' => 25.0],
'MA' => ['name' => 'Morocco', 'latitude' => 32.0, 'longitude' => -5.0],
'MC' => ['name' => 'Monaco', 'latitude' => 43.73, 'longitude' => 7.4],
'MD' => ['name' => 'Moldova', 'latitude' => 47.0, 'longitude' => 29.0],
'MG' => ['name' => 'Madagascar', 'latitude' => -20.0, 'longitude' => 47.0],
'MV' => ['name' => 'Maldives', 'latitude' => 3.25, 'longitude' => 73.0],
'MX' => ['name' => 'Mexico', 'latitude' => 23.0, 'longitude' => -102.0],
'MH' => ['name' => 'Marshall Islands', 'latitude' => 9.0, 'longitude' => 168.0],
'MK' => ['name' => 'North Macedonia', 'latitude' => 41.83, 'longitude' => 22.0],
'ML' => ['name' => 'Mali', 'latitude' => 17.0, 'longitude' => -4.0],
'MT' => ['name' => 'Malta', 'latitude' => 35.83, 'longitude' => 14.58],
'MM' => ['name' => 'Myanmar', 'latitude' => 22.0, 'longitude' => 98.0],
'ME' => ['name' => 'Montenegro', 'latitude' => 42.5, 'longitude' => 19.3],
'MN' => ['name' => 'Mongolia', 'latitude' => 46.0, 'longitude' => 105.0],
'MZ' => ['name' => 'Mozambique', 'latitude' => -18.25, 'longitude' => 35.0],
'MR' => ['name' => 'Mauritania', 'latitude' => 20.0, 'longitude' => -12.0],
'MU' => ['name' => 'Mauritius', 'latitude' => -20.28, 'longitude' => 57.55],
'MW' => ['name' => 'Malawi', 'latitude' => -13.5, 'longitude' => 34.0],
'MY' => ['name' => 'Malaysia', 'latitude' => 2.5, 'longitude' => 112.5],
'NA' => ['name' => 'Namibia', 'latitude' => -22.0, 'longitude' => 17.0],
'NE' => ['name' => 'Niger', 'latitude' => 16.0, 'longitude' => 8.0],
'NG' => ['name' => 'Nigeria', 'latitude' => 10.0, 'longitude' => 8.0],
'NI' => ['name' => 'Nicaragua', 'latitude' => 13.0, 'longitude' => -85.0],
'NL' => ['name' => 'Netherlands', 'latitude' => 52.5, 'longitude' => 5.75],
'NO' => ['name' => 'Norway', 'latitude' => 62.0, 'longitude' => 10.0],
'NP' => ['name' => 'Nepal', 'latitude' => 28.0, 'longitude' => 84.0],
'NR' => ['name' => 'Nauru', 'latitude' => -0.53, 'longitude' => 166.92],
'NZ' => ['name' => 'New Zealand', 'latitude' => -41.0, 'longitude' => 174.0],
'OM' => ['name' => 'Oman', 'latitude' => 21.0, 'longitude' => 57.0],
'PK' => ['name' => 'Pakistan', 'latitude' => 30.0, 'longitude' => 70.0],
'PS' => ['name' => 'Palestine', 'latitude' => 31.9, 'longitude' => 35.2],
'PA' => ['name' => 'Panama', 'latitude' => 9.0, 'longitude' => -80.0],
'PE' => ['name' => 'Peru', 'latitude' => -10.0, 'longitude' => -76.0],
'PH' => ['name' => 'Philippines', 'latitude' => 13.0, 'longitude' => 122.0],
'PW' => ['name' => 'Palau', 'latitude' => 7.5, 'longitude' => 134.5],
'PG' => ['name' => 'Papua New Guinea', 'latitude' => -6.0, 'longitude' => 147.0],
'PL' => ['name' => 'Poland', 'latitude' => 52.0, 'longitude' => 20.0],
'KP' => ['name' => 'North Korea', 'latitude' => 40.0, 'longitude' => 127.0],
'PT' => ['name' => 'Portugal', 'latitude' => 39.5, 'longitude' => -8.0],
'PY' => ['name' => 'Paraguay', 'latitude' => -23.0, 'longitude' => -58.0],
'QA' => ['name' => 'Qatar', 'latitude' => 25.5, 'longitude' => 51.25],
'RO' => ['name' => 'Romania', 'latitude' => 46.0, 'longitude' => 25.0],
'RU' => ['name' => 'Russia', 'latitude' => 60.0, 'longitude' => 100.0],
'RW' => ['name' => 'Rwanda', 'latitude' => -2.0, 'longitude' => 30.0],
'SA' => ['name' => 'Saudi Arabia', 'latitude' => 25.0, 'longitude' => 45.0],
'SD' => ['name' => 'Sudan', 'latitude' => 15.0, 'longitude' => 30.0],
'SN' => ['name' => 'Senegal', 'latitude' => 14.0, 'longitude' => -14.0],
'SG' => ['name' => 'Singapore', 'latitude' => 1.37, 'longitude' => 103.8],
'SB' => ['name' => 'Solomon Islands', 'latitude' => -8.0, 'longitude' => 159.0],
'SL' => ['name' => 'Sierra Leone', 'latitude' => 8.5, 'longitude' => -11.5],
'SV' => ['name' => 'El Salvador', 'latitude' => 13.83, 'longitude' => -88.92],
'SM' => ['name' => 'San Marino', 'latitude' => 43.77, 'longitude' => 12.42],
'SO' => ['name' => 'Somalia', 'latitude' => 10.0, 'longitude' => 49.0],
'RS' => ['name' => 'Serbia', 'latitude' => 44.0, 'longitude' => 21.0],
'SS' => ['name' => 'South Sudan', 'latitude' => 8.0, 'longitude' => 30.0],
'ST' => ['name' => 'São Tomé and Príncipe', 'latitude' => 1.0, 'longitude' => 7.0],
'SR' => ['name' => 'Suriname', 'latitude' => 4.0, 'longitude' => -56.0],
'SK' => ['name' => 'Slovakia', 'latitude' => 48.67, 'longitude' => 19.5],
'SI' => ['name' => 'Slovenia', 'latitude' => 46.0, 'longitude' => 15.0],
'SE' => ['name' => 'Sweden', 'latitude' => 62.0, 'longitude' => 15.0],
'SZ' => ['name' => 'Eswatini', 'latitude' => -26.5, 'longitude' => 31.5],
'SC' => ['name' => 'Seychelles', 'latitude' => -4.58, 'longitude' => 55.67],
'SY' => ['name' => 'Syria', 'latitude' => 35.0, 'longitude' => 38.0],
'TD' => ['name' => 'Chad', 'latitude' => 15.0, 'longitude' => 19.0],
'TG' => ['name' => 'Togo', 'latitude' => 8.0, 'longitude' => 1.17],
'TH' => ['name' => 'Thailand', 'latitude' => 15.0, 'longitude' => 100.0],
'TJ' => ['name' => 'Tajikistan', 'latitude' => 39.0, 'longitude' => 71.0],
'TM' => ['name' => 'Turkmenistan', 'latitude' => 40.0, 'longitude' => 60.0],
'TL' => ['name' => 'Timor-Leste', 'latitude' => -8.83, 'longitude' => 125.92],
'TO' => ['name' => 'Tonga', 'latitude' => -20.0, 'longitude' => -175.0],
'TT' => ['name' => 'Trinidad and Tobago', 'latitude' => 11.0, 'longitude' => -61.0],
'TN' => ['name' => 'Tunisia', 'latitude' => 34.0, 'longitude' => 9.0],
'TR' => ['name' => 'Turkey', 'latitude' => 39.0, 'longitude' => 35.0],
'TV' => ['name' => 'Tuvalu', 'latitude' => -8.0, 'longitude' => 178.0],
'TZ' => ['name' => 'Tanzania', 'latitude' => -6.0, 'longitude' => 35.0],
'TW' => ['name' => 'Taiwan', 'latitude' => 23.5, 'longitude' => 121.0],
'UG' => ['name' => 'Uganda', 'latitude' => 1.0, 'longitude' => 32.0],
'UA' => ['name' => 'Ukraine', 'latitude' => 49.0, 'longitude' => 32.0],
'UY' => ['name' => 'Uruguay', 'latitude' => -33.0, 'longitude' => -56.0],
'US' => ['name' => 'United States', 'latitude' => 38.0, 'longitude' => -97.0],
'UZ' => ['name' => 'Uzbekistan', 'latitude' => 41.0, 'longitude' => 64.0],
'VA' => ['name' => 'Vatican City', 'latitude' => 41.9, 'longitude' => 12.45],
'VC' => ['name' => 'Saint Vincent and the Grenadines', 'latitude' => 13.25, 'longitude' => -61.2],
'VE' => ['name' => 'Venezuela', 'latitude' => 8.0, 'longitude' => -66.0],
'VN' => ['name' => 'Vietnam', 'latitude' => 16.0, 'longitude' => 106.0],
'VU' => ['name' => 'Vanuatu', 'latitude' => -16.0, 'longitude' => 167.0],
'WS' => ['name' => 'Samoa', 'latitude' => -13.58, 'longitude' => -172.33],
'YE' => ['name' => 'Yemen', 'latitude' => 15.0, 'longitude' => 48.0],
'ZA' => ['name' => 'South Africa', 'latitude' => -29.0, 'longitude' => 24.0],
'ZM' => ['name' => 'Zambia', 'latitude' => -15.0, 'longitude' => 30.0],
'ZW' => ['name' => 'Zimbabwe', 'latitude' => -20.0, 'longitude' => 30.0],
];
+9 -9
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '16.1.0',
'version' => '17.0.1',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '13.1.1',
'version' => '15.0.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -77,7 +77,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '7.1.0',
'version' => '9.0.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -134,7 +134,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.6.0',
'version' => '0.7.1',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '6.2.0',
'version' => '6.2.2',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -245,7 +245,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '14.2.0',
'version' => '15.0.1',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -299,7 +299,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '6.2.0',
'version' => '9.0.2',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -371,7 +371,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '12.2.0',
'version' => '14.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -411,7 +411,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '6.2.0',
'version' => '8.0.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+277 -43
View File
@@ -4367,7 +4367,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -4398,6 +4398,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/document"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/documentList"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
}
],
"auth": {
"Project": []
}
@@ -4447,6 +4492,14 @@
"description": "Document data as JSON object.",
"x-example": "{}"
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON object.",
"x-example": null,
"items": {
"type": "object"
}
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
@@ -4455,11 +4508,192 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
}
}
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"tags": [
"databases"
],
"description": "Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be updated.",
"responses": {
"200": {
"description": "Documents List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/documentList"
}
}
}
}
},
"x-appwrite": {
"method": "updateDocuments",
"weight": 113,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/update-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-documents.md",
"rate-limit": 120,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<DATABASE_ID>"
},
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<COLLECTION_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}"
},
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"delete": {
"summary": "Delete documents",
"operationId": "databasesDeleteDocuments",
"tags": [
"databases"
],
"description": "Bulk delete documents using queries, if no queries are passed then all documents are deleted.",
"responses": {
"200": {
"description": "Documents List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/documentList"
}
}
}
}
},
"x-appwrite": {
"method": "deleteDocuments",
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/delete-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-documents.md",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<DATABASE_ID>"
},
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID. You can create a new collection using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection).",
"required": true,
"schema": {
"type": "string",
"x-example": "<COLLECTION_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
}
@@ -4680,7 +4914,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4763,7 +4997,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4848,7 +5082,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4962,7 +5196,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5035,7 +5269,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5086,7 +5320,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5137,7 +5371,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5188,7 +5422,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5239,7 +5473,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5290,7 +5524,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5341,7 +5575,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5392,7 +5626,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5443,7 +5677,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5494,7 +5728,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5545,7 +5779,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5627,7 +5861,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5701,7 +5935,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5786,7 +6020,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -5883,7 +6117,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5954,7 +6188,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6042,7 +6276,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6108,7 +6342,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6174,7 +6408,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6390,7 +6624,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6463,7 +6697,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6538,7 +6772,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6622,7 +6856,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6683,7 +6917,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6756,7 +6990,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6819,7 +7053,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6904,7 +7138,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7014,7 +7248,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7085,7 +7319,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7171,7 +7405,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7244,7 +7478,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7341,7 +7575,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7401,7 +7635,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 223,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+277 -43
View File
@@ -4367,7 +4367,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -4398,6 +4398,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/document"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/documentList"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
}
],
"auth": {
"Project": []
}
@@ -4447,6 +4492,14 @@
"description": "Document data as JSON object.",
"x-example": "{}"
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON object.",
"x-example": null,
"items": {
"type": "object"
}
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
@@ -4455,11 +4508,192 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
}
}
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"tags": [
"databases"
],
"description": "Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be updated.",
"responses": {
"200": {
"description": "Documents List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/documentList"
}
}
}
}
},
"x-appwrite": {
"method": "updateDocuments",
"weight": 113,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/update-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-documents.md",
"rate-limit": 120,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<DATABASE_ID>"
},
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<COLLECTION_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}"
},
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"delete": {
"summary": "Delete documents",
"operationId": "databasesDeleteDocuments",
"tags": [
"databases"
],
"description": "Bulk delete documents using queries, if no queries are passed then all documents are deleted.",
"responses": {
"200": {
"description": "Documents List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/documentList"
}
}
}
}
},
"x-appwrite": {
"method": "deleteDocuments",
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/delete-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-documents.md",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<DATABASE_ID>"
},
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID. You can create a new collection using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection).",
"required": true,
"schema": {
"type": "string",
"x-example": "<COLLECTION_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
}
@@ -4680,7 +4914,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4763,7 +4997,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4848,7 +5082,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4962,7 +5196,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5035,7 +5269,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5086,7 +5320,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5137,7 +5371,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5188,7 +5422,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5239,7 +5473,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5290,7 +5524,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5341,7 +5575,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5392,7 +5626,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5443,7 +5677,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5494,7 +5728,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5545,7 +5779,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5627,7 +5861,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5701,7 +5935,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5786,7 +6020,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -5883,7 +6117,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5954,7 +6188,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6042,7 +6276,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6108,7 +6342,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6174,7 +6408,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6390,7 +6624,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6463,7 +6697,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6538,7 +6772,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6622,7 +6856,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6683,7 +6917,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6756,7 +6990,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6819,7 +7053,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6904,7 +7138,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7014,7 +7248,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7085,7 +7319,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7171,7 +7405,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7244,7 +7478,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7341,7 +7575,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7401,7 +7635,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 223,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+275 -45
View File
@@ -4549,7 +4549,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -4576,6 +4576,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/document"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/documentList"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
}
],
"auth": {
"Project": []
}
@@ -4613,15 +4658,24 @@
"documentId": {
"type": "string",
"description": "Document 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.",
"default": null,
"default": "",
"x-example": "<DOCUMENT_ID>"
},
"data": {
"type": "object",
"description": "Document data as JSON object.",
"default": {},
"default": [],
"x-example": "{}"
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON object.",
"default": [],
"x-example": null,
"items": {
"type": "object"
}
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
@@ -4631,11 +4685,187 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
]
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"databases"
],
"description": "Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be updated.",
"responses": {
"200": {
"description": "Documents List",
"schema": {
"$ref": "#\/definitions\/documentList"
}
}
},
"x-appwrite": {
"method": "updateDocuments",
"weight": 113,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/update-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-documents.md",
"rate-limit": 120,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"type": "string",
"x-example": "<DATABASE_ID>",
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID.",
"required": true,
"type": "string",
"x-example": "<COLLECTION_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [],
"x-example": "{}"
},
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"default": [],
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
]
},
"delete": {
"summary": "Delete documents",
"operationId": "databasesDeleteDocuments",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"databases"
],
"description": "Bulk delete documents using queries, if no queries are passed then all documents are deleted.",
"responses": {
"200": {
"description": "Documents List",
"schema": {
"$ref": "#\/definitions\/documentList"
}
}
},
"x-appwrite": {
"method": "deleteDocuments",
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/delete-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-documents.md",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"type": "string",
"x-example": "<DATABASE_ID>",
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID. You can create a new collection using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection).",
"required": true,
"type": "string",
"x-example": "<COLLECTION_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"default": [],
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
]
@@ -4850,7 +5080,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4929,7 +5159,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5011,7 +5241,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5129,7 +5359,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5200,7 +5430,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5273,7 +5503,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5346,7 +5576,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5399,7 +5629,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5452,7 +5682,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5505,7 +5735,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5558,7 +5788,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5611,7 +5841,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5664,7 +5894,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5717,7 +5947,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5770,7 +6000,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5854,7 +6084,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5926,7 +6156,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6008,7 +6238,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6099,7 +6329,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6168,7 +6398,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6256,7 +6486,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6327,7 +6557,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6398,7 +6628,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6597,7 +6827,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6668,7 +6898,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6742,7 +6972,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6833,7 +7063,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6894,7 +7124,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6968,7 +7198,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7031,7 +7261,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7113,7 +7343,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7227,7 +7457,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7296,7 +7526,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7381,7 +7611,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7452,7 +7682,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7547,7 +7777,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7607,7 +7837,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 223,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+275 -45
View File
@@ -4549,7 +4549,7 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -4576,6 +4576,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/document"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/documentList"
}
],
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n"
}
],
"auth": {
"Project": []
}
@@ -4613,15 +4658,24 @@
"documentId": {
"type": "string",
"description": "Document 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.",
"default": null,
"default": "",
"x-example": "<DOCUMENT_ID>"
},
"data": {
"type": "object",
"description": "Document data as JSON object.",
"default": {},
"default": [],
"x-example": "{}"
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON object.",
"default": [],
"x-example": null,
"items": {
"type": "object"
}
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
@@ -4631,11 +4685,187 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
]
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"databases"
],
"description": "Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be updated.",
"responses": {
"200": {
"description": "Documents List",
"schema": {
"$ref": "#\/definitions\/documentList"
}
}
},
"x-appwrite": {
"method": "updateDocuments",
"weight": 113,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/update-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-documents.md",
"rate-limit": 120,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"type": "string",
"x-example": "<DATABASE_ID>",
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID.",
"required": true,
"type": "string",
"x-example": "<COLLECTION_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [],
"x-example": "{}"
},
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"default": [],
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
]
},
"delete": {
"summary": "Delete documents",
"operationId": "databasesDeleteDocuments",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"databases"
],
"description": "Bulk delete documents using queries, if no queries are passed then all documents are deleted.",
"responses": {
"200": {
"description": "Documents List",
"schema": {
"$ref": "#\/definitions\/documentList"
}
}
},
"x-appwrite": {
"method": "deleteDocuments",
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/delete-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-documents.md",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"type": "string",
"x-example": "<DATABASE_ID>",
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID. You can create a new collection using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection).",
"required": true,
"type": "string",
"x-example": "<COLLECTION_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"default": [],
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
]
@@ -4850,7 +5080,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4929,7 +5159,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5011,7 +5241,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5129,7 +5359,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5200,7 +5430,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5273,7 +5503,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5346,7 +5576,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5399,7 +5629,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5452,7 +5682,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5505,7 +5735,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5558,7 +5788,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5611,7 +5841,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5664,7 +5894,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5717,7 +5947,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5770,7 +6000,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5854,7 +6084,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5926,7 +6156,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6008,7 +6238,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6099,7 +6329,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6168,7 +6398,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6256,7 +6486,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6327,7 +6557,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6398,7 +6628,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6597,7 +6827,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6668,7 +6898,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6742,7 +6972,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6833,7 +7063,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6894,7 +7124,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6968,7 +7198,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7031,7 +7261,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7113,7 +7343,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7227,7 +7457,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7296,7 +7526,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7381,7 +7611,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7452,7 +7682,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7547,7 +7777,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7607,7 +7837,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 223,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -2006,6 +2006,7 @@ App::post('/v1/account/tokens/magic-url')
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
$replyTo = "";
if ($smtpEnabled) {
+415 -70
View File
@@ -3115,7 +3115,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default'])
->desc('Create document')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].create')
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'document.create')
@@ -3137,33 +3136,79 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
model: Response::MODEL_DOCUMENT,
)
],
contentType: ContentType::JSON
contentType: ContentType::JSON,
parameters: [
'databaseId' => ['optional' => false],
'collectionId' => ['optional' => false],
'documentId' => ['optional' => false],
'data' => ['optional' => false],
'permissions' => ['optional' => true],
]
),
new Method(
namespace: 'databases',
name: 'createDocuments',
description: '/docs/references/databases/create-document.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_DOCUMENT_LIST,
)
],
contentType: ContentType::JSON,
parameters: [
'databaseId' => ['optional' => false],
'collectionId' => ['optional' => false],
'documents' => ['optional' => false],
]
)
]
)
->param('databaseId', '', new UID(), 'Database ID.')
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('documentId', '', new CustomId(), 'Document 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('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('data', [], new JSON(), 'Document data as JSON object.', true)
->param('documents', [], new ArrayList(new JSON(), APP_LIMIT_DOCUMENTS_CREATE), 'Array of documents data as JSON object.', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
->inject('project')
->action(function (string $databaseId, ?string $documentId, string $collectionId, string|array|null $data, ?array $documents, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Document $project) {
$data = \is_string($data)
? \json_decode($data, true)
: $data;
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
$isBulk = true;
if (empty($data)) {
if (empty($data) && empty($documents)) {
throw new Exception(Exception::DOCUMENT_MISSING_DATA);
}
if (isset($data['$id'])) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is not allowed for creating new documents, try update instead');
if (!empty($data) && !empty($documents)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'You can only send one of the following parameters: data, documents');
}
if (!empty($data) && empty($documentId)) {
throw new Exception(Exception::DOCUMENT_MISSING_DATA, 'Document ID is required when creating a single document');
}
if (!empty($documents) && !empty($documentId)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "documentId" is disallowed when creating multiple documents, set "$id" in each document instead');
}
if (!empty($documents) && !empty($permissions)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "permissions" is disallowed when creating multiple documents, set "$permissions" in each document instead');
}
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if (!empty($data)) {
$isBulk = false;
$documents = [$data];
}
$database = Authorization::skip(fn () => $dbForProject->getDocument(
'databases',
$databaseId
));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@@ -3184,43 +3229,45 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
Database::PERMISSION_DELETE,
];
// Map aggregate permissions to into the set of individual permissions they represent.
$permissions = Permission::aggregate($permissions, $allowedPermissions);
// Add permissions for current the user if none were provided.
if (\is_null($permissions)) {
$permissions = [];
if (!empty($user->getId())) {
foreach ($allowedPermissions as $permission) {
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
}
$setPermissions = function (Document $document, ?array $permissions) use ($user, $allowedPermissions, $isAPIKey, $isPrivilegedUser, $isBulk) {
if ($isBulk) {
$permissions = $document['$permissions'] ?? null;
}
}
// Users can only manage their own roles, API keys and Admin users can manage any
if (!$isAPIKey && !$isPrivilegedUser) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
continue;
}
$role = (new Role(
$permission->getRole(),
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')');
$permissions = Permission::aggregate($permissions, $allowedPermissions);
// Add permissions for current the user if none were provided.
if (\is_null($permissions)) {
$permissions = [];
if (!empty($user->getId())) {
foreach ($allowedPermissions as $permission) {
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
}
}
}
}
$data['$collection'] = $collection->getId(); // Adding this param to make API easier for developers
$data['$id'] = $documentId == 'unique()' ? ID::unique() : $documentId;
$data['$permissions'] = $permissions;
$document = new Document($data);
// Users can only manage their own roles, API keys and Admin users can manage any
if (!$isAPIKey && !$isPrivilegedUser) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
continue;
}
$role = (new Role(
$permission->getRole(),
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')');
}
}
}
}
$document->setAttribute('$permissions', $permissions);
};
$operations = 0;
@@ -3306,21 +3353,50 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
};
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
$documents = array_map(function ($document) use ($collection, $permissions, $checkPermissions, $isBulk, $documentId, $setPermissions) {
$document['$collection'] = $collection->getId();
if (!$isBulk) {
$document['$id'] = $documentId == 'unique()' ? ID::unique() : $documentId;
} else {
if (empty($document['$id'])) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id must be set in each document when creating bulk documents');
}
$document['$id'] = $document['$id'] == 'unique()' ? ID::unique() : $document['$id'];
}
$document = new Document($document);
$setPermissions($document, $permissions);
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
return $document;
}, $documents);
try {
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document);
$dbForProject->createDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$documents,
APP_LIMIT_DATABASE_BATCH
);
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
} catch (DuplicateException $e) {
} catch (DuplicateException) {
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
} catch (NotFoundException $e) {
} catch (NotFoundException) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setContext('collection', $collection)
->setContext('database', $database);
// Add $collectionId and $databaseId for all documents
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$processDocument = function (Document $collection, Document &$document) use (&$processDocument, $dbForProject, $database) {
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -3352,34 +3428,35 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
};
$processDocument($collection, $document);
foreach ($documents as $document) {
$processDocument($collection, $document);
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations)
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations)
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$response->addHeader('X-Debug-Operations', $operations);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
->addHeader('X-Debug-Operations', $operations);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
if ($isBulk) {
$response->dynamic(new Document([
'total' => count($documents),
'documents' => $documents
]), Response::MODEL_DOCUMENT_LIST);
} else {
$queueForEvents
->setParam('documentId', $document->getId())
->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create');
$response->dynamic(
$documents[0],
Response::MODEL_DOCUMENT
);
}
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
});
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
@@ -3467,7 +3544,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$operations++;
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -3520,7 +3596,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations)
->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
$response->addHeader('X-Debug-Operations', $operations);
@@ -3661,7 +3737,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$processDocument($collection, $document);
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations)
->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
$response->addHeader('X-Debug-Operations', $operations);
@@ -3959,7 +4035,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$setCollection($collection, $newDocument);
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations)
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations);
$response->addHeader('X-Debug-Operations', $operations);
@@ -4037,6 +4113,155 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->setPayload($response->getPayload(), sensitive: $relationships);
});
App::patch('/v1/databases/:databaseId/collections/:collectionId/documents')
->desc('Update documents')
->groups(['api', 'database'])
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'documents.update')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'databases',
name: 'updateDocuments',
description: '/docs/references/databases/update-documents.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DOCUMENT_LIST,
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
->inject('requestTimestamp')
->inject('response')
->inject('dbForProject')
->inject('project')
->action(function (string $databaseId, string $collectionId, string|array $data, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Document $project) {
$data = \is_string($data)
? \json_decode($data, true)
: $data;
if (empty($data)) {
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
}
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$permissions = $data['$permissions'] ?? null;
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions, [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
]);
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = Authorization::getRoles();
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
continue;
}
$role = (new Role(
$permission->getRole(),
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
}
if (!\is_null($permissions)) {
$data['$permissions'] = $permissions;
}
$partialDocument = new Document($data);
$documents = $dbForProject->withRequestTimestamp(
$requestTimestamp,
fn () => $dbForProject->updateDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$partialDocument,
$queries,
APP_LIMIT_DATABASE_BATCH
)
);
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$related = $document->getAttribute($relationship->getAttribute('key'));
if (empty($related)) {
continue;
}
if (!\is_array($related)) {
$related = [$related];
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
$relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
foreach ($related as $relation) {
if ($relation instanceof Document) {
$processDocument($relatedCollection, $relation);
}
}
}
};
foreach ($documents as $document) {
$processDocument($collection, $document);
}
$response->dynamic(new Document([
'total' => \count($documents),
'documents' => $documents
]), Response::MODEL_DOCUMENT_LIST);
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default'])
->desc('Delete document')
@@ -4162,9 +4387,129 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->setContext('database', $database)
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
$queueForStatsUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$response->noContent();
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents')
->desc('Delete documents')
->groups(['api', 'database'])
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'documents.delete')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'databases',
name: 'deleteDocuments',
description: '/docs/references/databases/delete-documents.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DOCUMENT_LIST,
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
->inject('requestTimestamp')
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('project')
->action(function (string $databaseId, string $collectionId, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Document $project) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$documents = $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $queries) {
return $dbForProject->deleteDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$queries,
APP_LIMIT_DATABASE_BATCH
);
});
// DB Storage Calculation
$queueForStatsUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$processDocument = (function (Document $collection, Document &$document) use (&$processDocument, $dbForProject, $database): bool {
if ($document->isEmpty()) {
return false;
}
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$related = $document->getAttribute($relationship->getAttribute('key'));
if (empty($related)) {
continue;
}
if (!\is_array($related)) {
$relations = [$related];
} else {
$relations = $related;
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
$relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId));
foreach ($relations as $index => $doc) {
if ($doc instanceof Document) {
if (!$processDocument($relatedCollection, $doc)) {
unset($relations[$index]);
}
}
}
if (\is_array($related)) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} elseif (empty($relations)) {
$document->setAttribute($relationship->getAttribute('key'), null);
}
}
return true;
});
$response->dynamic(new Document([
'total' => \count($documents),
'documents' => $documents,
]), Response::MODEL_DOCUMENT_LIST);
});
App::get('/v1/databases/usage')
->desc('Get databases usage stats')
->groups(['api', 'database', 'usage'])
+2
View File
@@ -387,6 +387,8 @@ App::post('/v1/functions')
'resourceInternalId' => $function->getInternalId(),
'status' => 'verified',
'certificateId' => '',
'owner' => 'Appwrite',
'region' => $project->getAttribute('region')
]))
);
+2 -2
View File
@@ -118,7 +118,7 @@ App::get('/v1/locale/countries')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
$list = Config::getParam('locale-countries'); /* @var $list array */
$list = array_keys(Config::getParam('locale-countries')); /* @var $list array */
$output = [];
foreach ($list as $value) {
@@ -229,7 +229,7 @@ App::get('/v1/locale/continents')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
$list = Config::getParam('locale-continents');
$list = array_keys(Config::getParam('locale-continents'));
foreach ($list as $value) {
$output[] = new Document([
+2
View File
@@ -130,6 +130,8 @@ App::post('/v1/proxy/rules')
'resourceId' => $resourceId,
'resourceInternalId' => $resourceInternalId,
'certificateId' => '',
'owner' => '',
'region' => $project->getAttribute('region')
]);
$status = 'created';
+10 -2
View File
@@ -582,6 +582,12 @@ App::init()
]);
}
$owner = '';
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) {
$owner = 'Appwrite';
}
if ($domainDocument->isEmpty()) {
$domainDocument = new Document([
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
@@ -589,8 +595,10 @@ App::init()
'domain' => $domain->get(),
'resourceType' => 'api',
'status' => 'verifying',
'projectId' => 'console',
'projectInternalId' => 'console'
'projectId' => $console->getId(),
'projectInternalId' => $console->getInternalId(),
'owner' => $owner,
'region' => $console->getAttribute('region')
]);
$domainDocument = $dbForPlatform->createDocument('rules', $domainDocument);
+9
View File
@@ -92,10 +92,17 @@ $eventDatabaseListener = function (Document $project, Document $document, Respon
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$value = 1;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$value = -1;
}
if ($event === Database::EVENT_DOCUMENTS_DELETE) {
$value = -1 * $document->getAttribute('modified', 0);
} elseif ($event === Database::EVENT_DOCUMENTS_CREATE) {
$value = $document->getAttribute('modified', 0);
}
switch (true) {
case $document->getCollection() === 'teams':
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
@@ -508,6 +515,8 @@ App::init()
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
+9 -1891
View File
File diff suppressed because it is too large Load Diff
+37
View File
@@ -0,0 +1,37 @@
<?php
use Utopia\Config\Config;
Config::load('events', __DIR__ . '/../config/events.php');
Config::load('auth', __DIR__ . '/../config/auth.php');
Config::load('apis', __DIR__ . '/../config/apis.php'); // List of APIs
Config::load('errors', __DIR__ . '/../config/errors.php');
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php');
Config::load('platforms', __DIR__ . '/../config/platforms.php');
Config::load('console', __DIR__ . '/../config/console.php');
Config::load('collections', __DIR__ . '/../config/collections.php');
Config::load('runtimes', __DIR__ . '/../config/runtimes.php');
Config::load('runtimes-v2', __DIR__ . '/../config/runtimes-v2.php');
Config::load('usage', __DIR__ . '/../config/usage.php');
Config::load('roles', __DIR__ . '/../config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__ . '/../config/scopes.php'); // User roles and scopes
Config::load('services', __DIR__ . '/../config/services.php'); // List of services
Config::load('variables', __DIR__ . '/../config/variables.php'); // List of env variables
Config::load('regions', __DIR__ . '/../config/regions.php'); // List of available regions
Config::load('avatar-browsers', __DIR__ . '/../config/avatars/browsers.php');
Config::load('avatar-credit-cards', __DIR__ . '/../config/avatars/credit-cards.php');
Config::load('avatar-flags', __DIR__ . '/../config/avatars/flags.php');
Config::load('locale-codes', __DIR__ . '/../config/locale/codes.php');
Config::load('locale-currencies', __DIR__ . '/../config/locale/currencies.php');
Config::load('locale-eu', __DIR__ . '/../config/locale/eu.php');
Config::load('locale-languages', __DIR__ . '/../config/locale/languages.php');
Config::load('locale-phones', __DIR__ . '/../config/locale/phones.php');
Config::load('locale-countries', __DIR__ . '/../config/locale/countries.php');
Config::load('locale-continents', __DIR__ . '/../config/locale/continents.php');
Config::load('locale-templates', __DIR__ . '/../config/locale/templates.php');
Config::load('storage-logos', __DIR__ . '/../config/storage/logos.php');
Config::load('storage-mimes', __DIR__ . '/../config/storage/mimes.php');
Config::load('storage-inputs', __DIR__ . '/../config/storage/inputs.php');
Config::load('storage-outputs', __DIR__ . '/../config/storage/outputs.php');
Config::load('runtime-specifications', __DIR__ . '/../config/runtimes/specifications.php');
Config::load('function-templates', __DIR__ . '/../config/function-templates.php');
+241
View File
@@ -0,0 +1,241 @@
<?php
use Appwrite\Functions\Specification;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = ''; // Default security email address
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
const APP_MODE_DEFAULT = 'default';
const APP_MODE_ADMIN = 'admin';
const APP_PAGING_LIMIT = 12;
const APP_LIMIT_COUNT = 5000;
const APP_LIMIT_USERS = 10_000;
const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
const APP_LIMIT_USER_SESSIONS_MAX = 100;
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
const APP_LIMIT_ANTIVIRUS = 20_000_000; //20MB
const APP_LIMIT_ENCRYPTION = 20_000_000; //20MB
const APP_LIMIT_COMPRESSION = 20_000_000; //20MB
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
const APP_LIMIT_SUBQUERY = 1000;
const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
const APP_LIMIT_DOCUMENTS_CREATE = 5_000; // Default maximum of how many documents can be inserted in a single request
const APP_LIMIT_DATABASE_BATCH = 1_000; // Default batch size for database operations
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4318;
const APP_VERSION_STABLE = '1.6.2';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
const APP_DATABASE_ATTRIBUTE_URL = 'url';
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char
const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
const APP_DATABASE_QUERY_MAX_VALUES = 500;
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_BUILDS = '/storage/builds';
const APP_STORAGE_CACHE = '/storage/cache';
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
const APP_STORAGE_CONFIG = '/storage/config';
const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite';
const APP_SOCIAL_TWITTER_HANDLE = 'appwrite';
const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
const APP_HOSTNAME_INTERNAL = 'appwrite';
const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
const APP_FUNCTION_CPUS_DEFAULT = 0.5;
const APP_FUNCTION_MEMORY_DEFAULT = 512;
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
// Database Worker Types
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute';
const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
const DATABASE_TYPE_DELETE_COLLECTION = 'deleteCollection';
const DATABASE_TYPE_DELETE_DATABASE = 'deleteDatabase';
// Build Worker Types
const BUILD_TYPE_DEPLOYMENT = 'deployment';
const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_PROJECTS = 'projects';
const DELETE_TYPE_FUNCTIONS = 'functions';
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
const DELETE_TYPE_USERS = 'users';
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_USAGE = 'usage';
const DELETE_TYPE_REALTIME = 'realtime';
const DELETE_TYPE_BUCKETS = 'buckets';
const DELETE_TYPE_INSTALLATIONS = 'installations';
const DELETE_TYPE_RULES = 'rules';
const DELETE_TYPE_SESSIONS = 'sessions';
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
const DELETE_TYPE_SCHEDULES = 'schedules';
const DELETE_TYPE_TOPIC = 'topic';
const DELETE_TYPE_TARGET = 'target';
const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
const DELETE_TYPE_MAINTENANCE = 'maintenance';
// Message types
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
// Mail Types
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
const MAIL_TYPE_RECOVERY = 'recovery';
const MAIL_TYPE_INVITATION = 'invitation';
const MAIL_TYPE_CERTIFICATE = 'certificate';
// Auth Types
const APP_AUTH_TYPE_SESSION = 'Session';
const APP_AUTH_TYPE_JWT = 'JWT';
const APP_AUTH_TYPE_KEY = 'Key';
const APP_AUTH_TYPE_ADMIN = 'Admin';
// Response related
const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
// Function headers
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
// Message types
const MESSAGE_TYPE_EMAIL = 'email';
const MESSAGE_TYPE_SMS = 'sms';
const MESSAGE_TYPE_PUSH = 'push';
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_DYNAMIC = 'dynamic';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
const METRIC_WEBHOOKS_SENT = 'webhooks.events.sent';
const METRIC_WEBHOOKS_FAILED = 'webhooks.events.failed';
const METRIC_WEBHOOK_ID_SENT = '{webhookInternalId}.webhooks.events.sent';
const METRIC_WEBHOOK_ID_FAILED = '{webhookInternalId}.webhooks.events.failed';
const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
const METRIC_MESSAGES = 'messages';
const METRIC_MESSAGES_SENT = METRIC_MESSAGES . '.sent';
const METRIC_MESSAGES_FAILED = METRIC_MESSAGES . '.failed';
const METRIC_MESSAGES_TYPE = METRIC_MESSAGES . '.{type}';
const METRIC_MESSAGES_TYPE_SENT = METRIC_MESSAGES . '.{type}.sent';
const METRIC_MESSAGES_TYPE_FAILED = METRIC_MESSAGES . '.{type}.failed';
const METRIC_MESSAGES_TYPE_PROVIDER = METRIC_MESSAGES . '.{type}.{provider}';
const METRIC_MESSAGES_TYPE_PROVIDER_SENT = METRIC_MESSAGES . '.{type}.{provider}.sent';
const METRIC_MESSAGES_TYPE_PROVIDER_FAILED = METRIC_MESSAGES . '.{type}.{provider}.failed';
const METRIC_SESSIONS = 'sessions';
const METRIC_DATABASES = 'databases';
const METRIC_COLLECTIONS = 'collections';
const METRIC_DATABASES_STORAGE = 'databases.storage';
const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections';
const METRIC_DATABASE_ID_STORAGE = '{databaseInternalId}.databases.storage';
const METRIC_DOCUMENTS = 'documents';
const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage';
const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads';
const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads';
const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes';
const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes';
const METRIC_BUCKETS = 'buckets';
const METRIC_FILES = 'files';
const METRIC_FILES_STORAGE = 'files.storage';
const METRIC_FILES_TRANSFORMATIONS = 'files.transformations';
const METRIC_BUCKET_ID_FILES_TRANSFORMATIONS = '{bucketInternalId}.files.transformations';
const METRIC_FILES_IMAGES_TRANSFORMED = 'files.imagesTransformed';
const METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED = '{bucketInternalId}.files.imagesTransformed';
const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files';
const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage';
const METRIC_FUNCTIONS = 'functions';
const METRIC_DEPLOYMENTS = 'deployments';
const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
const METRIC_BUILDS = 'builds';
const METRIC_BUILDS_SUCCESS = 'builds.success';
const METRIC_BUILDS_FAILED = 'builds.failed';
const METRIC_BUILDS_STORAGE = 'builds.storage';
const METRIC_BUILDS_COMPUTE = 'builds.compute';
const METRIC_BUILDS_COMPUTE_SUCCESS = 'builds.compute.success';
const METRIC_BUILDS_COMPUTE_FAILED = 'builds.compute.failed';
const METRIC_BUILDS_MB_SECONDS = 'builds.mbSeconds';
const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds';
const METRIC_FUNCTION_ID_BUILDS_SUCCESS = '{functionInternalId}.builds.success';
const METRIC_FUNCTION_ID_BUILDS_FAILED = '{functionInternalId}.builds.failed';
const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS = '{functionInternalId}.builds.compute.success';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED = '{functionInternalId}.builds.compute.failed';
const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
const METRIC_EXECUTIONS = 'executions';
const METRIC_EXECUTIONS_COMPUTE = 'executions.compute';
const METRIC_EXECUTIONS_MB_SECONDS = 'executions.mbSeconds';
const METRIC_FUNCTION_ID_EXECUTIONS = '{functionInternalId}.executions';
const METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE = '{functionInternalId}.executions.compute';
const METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS = '{functionInternalId}.executions.mbSeconds';
const METRIC_NETWORK_REQUESTS = 'network.requests';
const METRIC_NETWORK_INBOUND = 'network.inbound';
const METRIC_NETWORK_OUTBOUND = 'network.outbound';
const METRIC_MAU = 'users.mau';
const METRIC_DAU = 'users.dau';
const METRIC_WAU = 'users.wau';
const METRIC_WEBHOOKS = 'webhooks';
const METRIC_PLATFORMS = 'platforms';
const METRIC_PROVIDERS = 'providers';
const METRIC_TOPICS = 'topics';
const METRIC_TARGETS = 'targets';
const METRIC_PROVIDER_TYPE_TARGETS = '{providerType}.targets';
const METRIC_KEYS = 'keys';
const METRIC_DOMAINS = 'domains';
const METRIC_RESOURCE_TYPE_ID_BUILDS = '{resourceType}.{resourceInternalId}.builds';
const METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE = '{resourceType}.{resourceInternalId}.builds.storage';
const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
// Resource types
const RESOURCE_TYPE_PROJECTS = 'projects';
const RESOURCE_TYPE_FUNCTIONS = 'functions';
const RESOURCE_TYPE_DATABASES = 'databases';
const RESOURCE_TYPE_BUCKETS = 'buckets';
const RESOURCE_TYPE_PROVIDERS = 'providers';
const RESOURCE_TYPE_TOPICS = 'topics';
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
+404
View File
@@ -0,0 +1,404 @@
<?php
use Appwrite\OpenSSL\OpenSSL;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\System\System;
Database::addFilter(
'casting',
function (mixed $value) {
return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
},
function (mixed $value) {
if (is_null($value)) {
return;
}
return json_decode($value, true)['value'];
}
);
Database::addFilter(
'enum',
function (mixed $value, Document $attribute) {
if ($attribute->isSet('elements')) {
$attribute->removeAttribute('elements');
}
return $value;
},
function (mixed $value, Document $attribute) {
$formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
if (isset($formatOptions['elements'])) {
$attribute->setAttribute('elements', $formatOptions['elements']);
}
return $value;
}
);
Database::addFilter(
'range',
function (mixed $value, Document $attribute) {
if ($attribute->isSet('min')) {
$attribute->removeAttribute('min');
}
if ($attribute->isSet('max')) {
$attribute->removeAttribute('max');
}
return $value;
},
function (mixed $value, Document $attribute) {
$formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
$attribute
->setAttribute('min', $formatOptions['min'])
->setAttribute('max', $formatOptions['max']);
}
return $value;
}
);
Database::addFilter(
'subQueryAttributes',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
$attributes = $database->find('attributes', [
Query::equal('collectionInternalId', [$document->getInternalId()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForAttributes()),
]);
foreach ($attributes as $attribute) {
if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
$options = $attribute->getAttribute('options');
foreach ($options as $key => $value) {
$attribute->setAttribute($key, $value);
}
$attribute->removeAttribute('options');
}
}
return $attributes;
}
);
Database::addFilter(
'subQueryIndexes',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
->find('indexes', [
Query::equal('collectionInternalId', [$document->getInternalId()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForIndexes()),
]);
}
);
Database::addFilter(
'subQueryPlatforms',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
->find('platforms', [
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
);
Database::addFilter(
'subQueryKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
->find('keys', [
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
);
Database::addFilter(
'subQueryWebhooks',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
->find('webhooks', [
Query::equal('projectInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
);
Database::addFilter(
'subQuerySessions',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database->find('sessions', [
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryTokens',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('tokens', [
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryChallenges',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('challenges', [
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryAuthenticators',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('authenticators', [
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryMemberships',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('memberships', [
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryVariables',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
->find('variables', [
Query::equal('resourceInternalId', [$document->getInternalId()]),
Query::equal('resourceType', ['function']),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
);
Database::addFilter(
'encrypt',
function (mixed $value) {
$key = System::getEnv('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
return json_encode([
'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
'iv' => \bin2hex($iv),
'tag' => \bin2hex($tag ?? ''),
'version' => '1',
]);
},
function (mixed $value) {
if (is_null($value)) {
return;
}
$value = json_decode($value, true);
$key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
}
);
Database::addFilter(
'subQueryProjectVariables',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
->find('variables', [
Query::equal('resourceType', ['project']),
Query::limit(APP_LIMIT_SUBQUERY)
]);
}
);
Database::addFilter(
'userSearch',
function (mixed $value, Document $user) {
$searchValues = [
$user->getId(),
$user->getAttribute('email', ''),
$user->getAttribute('name', ''),
$user->getAttribute('phone', '')
];
foreach ($user->getAttribute('labels', []) as $label) {
$searchValues[] = 'label:' . $label;
}
$search = implode(' ', \array_filter($searchValues));
return $search;
},
function (mixed $value) {
return $value;
}
);
Database::addFilter(
'subQueryTargets',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
->find('targets', [
Query::equal('userInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBQUERY)
]));
}
);
Database::addFilter(
'subQueryTopicTargets',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
$targetIds = Authorization::skip(fn () => \array_map(
fn ($document) => $document->getAttribute('targetInternalId'),
$database->find('subscribers', [
Query::equal('topicInternalId', [$document->getInternalId()]),
Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
])
));
if (\count($targetIds) > 0) {
return $database->skipValidation(fn () => $database->find('targets', [
Query::equal('$internalId', $targetIds)
]));
}
return [];
}
);
Database::addFilter(
'providerSearch',
function (mixed $value, Document $provider) {
$searchValues = [
$provider->getId(),
$provider->getAttribute('name', ''),
$provider->getAttribute('provider', ''),
$provider->getAttribute('type', '')
];
$search = \implode(' ', \array_filter($searchValues));
return $search;
},
function (mixed $value) {
return $value;
}
);
Database::addFilter(
'topicSearch',
function (mixed $value, Document $topic) {
$searchValues = [
$topic->getId(),
$topic->getAttribute('name', ''),
$topic->getAttribute('description', ''),
];
$search = \implode(' ', \array_filter($searchValues));
return $search;
},
function (mixed $value) {
return $value;
}
);
Database::addFilter(
'messageSearch',
function (mixed $value, Document $message) {
$searchValues = [
$message->getId(),
$message->getAttribute('description', ''),
$message->getAttribute('status', ''),
];
$data = \json_decode($message->getAttribute('data', []), true);
$providerType = $message->getAttribute('providerType', '');
switch ($providerType) {
case MESSAGE_TYPE_EMAIL:
$searchValues[] = $data['subject'];
$searchValues[] = MESSAGE_TYPE_EMAIL;
break;
case MESSAGE_TYPE_SMS:
$searchValues[] = $data['content'];
$searchValues[] = MESSAGE_TYPE_SMS;
break;
case MESSAGE_TYPE_PUSH:
$searchValues[] = $data['title'] ?? '';
$searchValues[] = MESSAGE_TYPE_PUSH;
break;
}
$search = \implode(' ', \array_filter($searchValues));
return $search;
},
function (mixed $value) {
return $value;
}
);
+43
View File
@@ -0,0 +1,43 @@
<?php
use Appwrite\Network\Validator\Email;
use Utopia\Database\Database;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Structure;
use Utopia\Validator\IP;
use Utopia\Validator\Range;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
return new Email();
}, Database::VAR_STRING);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
return new DatetimeValidator();
}, Database::VAR_DATETIME);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
$elements = $attribute['formatOptions']['elements'] ?? [];
return new WhiteList($elements, true);
}, Database::VAR_STRING);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () {
return new IP();
}, Database::VAR_STRING);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () {
return new URL();
}, Database::VAR_STRING);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
return new Range($min, $max, Range::TYPE_INTEGER);
}, Database::VAR_INTEGER);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
$min = $attribute['formatOptions']['min'] ?? -INF;
$max = $attribute['formatOptions']['max'] ?? INF;
return new Range($min, $max, Range::TYPE_FLOAT);
}, Database::VAR_FLOAT);
+23
View File
@@ -0,0 +1,23 @@
<?php
use Utopia\Config\Config;
use Utopia\Locale\Locale;
Locale::$exceptions = false;
$locales = Config::getParam('locale-codes', []);
foreach ($locales as $locale) {
$code = $locale['code'];
$path = __DIR__ . '/../config/locale/translations/' . $code . '.json';
if (!\file_exists($path)) {
$path = __DIR__ . '/../config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar`
if (!\file_exists($path)) {
$path = __DIR__ . '/../config/locale/translations/en.json'; // if none translation exists, use default from `en.json`
}
}
Locale::setLanguageFromJSON($code, $path);
}
+340
View File
@@ -0,0 +1,340 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\GraphQL\Promises\Adapter\Swoole;
use Appwrite\Hooks\Hooks;
use Appwrite\PubSub\Adapter\Redis as PubSub;
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;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Adapter\SQL;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
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\Group;
use Utopia\Pools\Pool;
use Utopia\Queue;
use Utopia\Registry\Registry;
use Utopia\System\System;
$register = new Registry();
App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
if (!App::isProduction()) {
// Allow specific domains to skip public domain validation in dev environment
// Useful for existing tests involving webhooks
PublicDomain::allow(['request-catcher']);
}
$register->set('logger', function () {
// Register error logger
$providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
if (empty($providerConfig)) {
return;
}
try {
$loggingProvider = new DSN($providerConfig ?? '');
$providerName = $loggingProvider->getScheme();
$providerConfig = match ($providerName) {
'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => 'https://' . $loggingProvider->getHost()],
'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
default => ['key' => $loggingProvider->getHost()],
};
} catch (Throwable $th) {
// Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables
Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage());
$configChunks = \explode(";", $providerConfig);
$providerConfig = match ($providerName) {
'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
default => ['key' => $providerConfig],
};
}
if (empty($providerName) || empty($providerConfig)) {
return;
}
if (!Logger::hasProvider($providerName)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
}
try {
$adapter = match ($providerName) {
'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
'raygun' => new Raygun($providerConfig['key']),
'appsignal' => new AppSignal($providerConfig['key']),
default => null
};
} catch (Throwable $th) {
$adapter = null;
}
if ($adapter === null) {
Console::error("Logging provider not supported. Logging is disabled");
return;
}
return new Logger($adapter);
});
$register->set('pools', function () {
$group = new Group();
$fallbackForDB = 'db_main=' . AppwriteURL::unparse([
'scheme' => 'mariadb',
'host' => System::getEnv('_APP_DB_HOST', 'mariadb'),
'port' => System::getEnv('_APP_DB_PORT', '3306'),
'user' => System::getEnv('_APP_DB_USER', ''),
'pass' => System::getEnv('_APP_DB_PASS', ''),
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
]);
$fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([
'scheme' => 'redis',
'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
'user' => System::getEnv('_APP_REDIS_USER', ''),
'pass' => System::getEnv('_APP_REDIS_PASS', ''),
]);
$connections = [
'console' => [
'type' => 'database',
'dsns' => $fallbackForDB,
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
],
'database' => [
'type' => 'database',
'dsns' => $fallbackForDB,
'multiple' => true,
'schemes' => ['mariadb', 'mysql'],
],
'logs' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
],
'publisher' => [
'type' => 'publisher',
'dsns' => $fallbackForRedis,
'multiple' => false,
'schemes' => ['redis'],
],
'consumer' => [
'type' => 'consumer',
'dsns' => $fallbackForRedis,
'multiple' => false,
'schemes' => ['redis'],
],
'pubsub' => [
'type' => 'pubsub',
'dsns' => $fallbackForRedis,
'multiple' => false,
'schemes' => ['redis'],
],
'cache' => [
'type' => 'cache',
'dsns' => $fallbackForRedis,
'multiple' => true,
'schemes' => ['redis'],
],
];
$maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151);
$instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14);
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
if ($multiprocessing) {
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
} else {
$workerCount = 1;
}
if ($workerCount > $instanceConnections) {
throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
}
$poolSize = (int)($instanceConnections / $workerCount);
foreach ($connections as $key => $connection) {
$type = $connection['type'] ?? '';
$multiple = $connection['multiple'] ?? false;
$schemes = $connection['schemes'] ?? [];
$config = [];
$dsns = explode(',', $connection['dsns'] ?? '');
foreach ($dsns as &$dsn) {
$dsn = explode('=', $dsn);
$name = ($multiple) ? $key . '_' . $dsn[0] : $key;
$dsn = $dsn[1] ?? '';
$config[] = $name;
if (empty($dsn)) {
//throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}");
continue;
}
$dsn = new DSN($dsn);
$dsnHost = $dsn->getHost();
$dsnPort = $dsn->getPort();
$dsnUser = $dsn->getUser();
$dsnPass = $dsn->getPassword();
$dsnScheme = $dsn->getScheme();
$dsnDatabase = $dsn->getPath();
if (!in_array($dsnScheme, $schemes)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme");
}
/**
* Get Resource
*
* Creation could be reused across connection types like database, cache, queue, etc.
*
* Resource assignment to an adapter will happen below.
*/
$resource = match ($dsnScheme) {
'mysql',
'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true
));
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new \Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
if ($dsnPass) {
$redis->auth($dsnPass);
}
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
},
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
};
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
// Get Adapter
switch ($type) {
case 'database':
$adapter = match ($dsn->getScheme()) {
'mariadb' => new MariaDB($resource()),
'mysql' => new MySQL($resource()),
default => null
};
$adapter->setDatabase($dsn->getPath());
return $adapter;
case 'pubsub':
return match ($dsn->getScheme()) {
'redis' => new PubSub($resource()),
default => null
};
case 'publisher':
case 'consumer':
return match ($dsn->getScheme()) {
'redis' => new Queue\Broker\Redis(new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort())),
default => null
};
case 'cache':
return match ($dsn->getScheme()) {
'redis' => new RedisCache($resource()),
default => null
};
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
}
});
$group->add($pool);
}
Config::setParam('pools-' . $key, $config);
}
return $group;
});
$register->set('db', function () {
// This is usually for our workers or CLI commands scope
$dbHost = System::getEnv('_APP_DB_HOST', '');
$dbPort = System::getEnv('_APP_DB_PORT', '');
$dbUser = System::getEnv('_APP_DB_USER', '');
$dbPass = System::getEnv('_APP_DB_PASS', '');
$dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
return new PDO(
"mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
$dbUser,
$dbPass,
SQL::getPDOAttributes()
);
});
$register->set('smtp', function () {
$mail = new PHPMailer(true);
$mail->isSMTP();
$username = System::getEnv('_APP_SMTP_USERNAME');
$password = System::getEnv('_APP_SMTP_PASSWORD');
$mail->XMailer = 'Appwrite Mailer';
$mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp');
$mail->Port = System::getEnv('_APP_SMTP_PORT', 25);
$mail->SMTPAuth = !empty($username) && !empty($password);
$mail->Username = $username;
$mail->Password = $password;
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
$mail->SMTPAutoTLS = false;
$mail->CharSet = 'UTF-8';
$from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
$email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$mail->setFrom($email, $from);
$mail->addReplyTo($email, $from);
$mail->isHTML(true);
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2024-09.mmdb');
});
$register->set('passwordsDictionary', function () {
$content = \file_get_contents(__DIR__ . '/../assets/security/10k-common-passwords');
$content = explode("\n", $content);
$content = array_flip($content);
return $content;
});
$register->set('promiseAdapter', function () {
return new Swoole();
});
$register->set('hooks', function () {
return new Hooks();
});
+824
View File
@@ -0,0 +1,824 @@
<?php
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Key;
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\GraphQL\Schema;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Request;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Queue\Publisher;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Backblaze;
use Utopia\Storage\Device\DOSpaces;
use Utopia\Storage\Device\Linode;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Device\S3;
use Utopia\Storage\Device\Wasabi;
use Utopia\Storage\Storage;
use Utopia\System\System;
use Utopia\Validator\Hostname;
use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub;
// Runtime Execution
App::setResource('log', fn () => new Log());
App::setResource('logger', function ($register) {
return $register->get('logger');
}, ['register']);
App::setResource('hooks', function ($register) {
return $register->get('hooks');
}, ['register']);
App::setResource('register', fn () => $register);
App::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en')));
App::setResource('localeCodes', function () {
return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []));
});
// Queues
App::setResource('publisher', function (Group $pools) {
return $pools->get('publisher')->pop()->getResource();
}, ['pools']);
App::setResource('consumer', function (Group $pools) {
return $pools->get('consumer')->pop()->getResource();
}, ['pools']);
App::setResource('queueForMessaging', function (Publisher $publisher) {
return new Messaging($publisher);
}, ['publisher']);
App::setResource('queueForMails', function (Publisher $publisher) {
return new Mail($publisher);
}, ['publisher']);
App::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
App::setResource('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
App::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
App::setResource('queueForEvents', function (Publisher $publisher) {
return new Event($publisher);
}, ['publisher']);
App::setResource('queueForWebhooks', function (Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
App::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
App::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
App::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
}, ['publisher']);
App::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
App::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
App::setResource('queueForMigrations', function (Publisher $publisher) {
return new Migration($publisher);
}, ['publisher']);
App::setResource('clients', function ($request, $console, $project) {
$console->setAttribute('platforms', [ // Always allow current host
'$collection' => ID::custom('platforms'),
'name' => 'Current Host',
'type' => Origin::CLIENT_TYPE_WEB,
'hostname' => $request->getHostname(),
], Document::SET_TYPE_APPEND);
$hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
$validator = new Hostname();
foreach ($hostnames as $hostname) {
$hostname = trim($hostname);
if (!$validator->isValid($hostname)) {
continue;
}
$console->setAttribute('platforms', [
'$collection' => ID::custom('platforms'),
'type' => Origin::CLIENT_TYPE_WEB,
'name' => $hostname,
'hostname' => $hostname,
], Document::SET_TYPE_APPEND);
}
/**
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
*/
$clientsConsole = \array_map(
fn ($node) => $node['hostname'],
\array_filter(
$console->getAttribute('platforms', []),
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
)
);
$clients = $clientsConsole;
$platforms = $project->getAttribute('platforms', []);
foreach ($platforms as $node) {
if (
isset($node['type']) &&
($node['type'] === Origin::CLIENT_TYPE_WEB ||
$node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
!empty($node['hostname'])
) {
$clients[] = $node['hostname'];
}
}
return \array_unique($clients);
}, ['request', 'console', 'project']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var string $mode */
Authorization::setDefaultStatus(true);
Auth::setCookieName('a_session_' . $project->getId());
if (APP_MODE_ADMIN === $mode) {
Auth::setCookieName('a_session_' . $console->getId());
}
$session = Auth::decodeSession(
$request->getCookie(
Auth::$cookieName, // Get sessions
$request->getCookie(Auth::$cookieName . '_legacy', '')
)
);
// Get session from header for SSR clients
if (empty($session['id']) && empty($session['secret'])) {
$sessionHeader = $request->getHeader('x-appwrite-session', '');
if (!empty($sessionHeader)) {
$session = Auth::decodeSession($sessionHeader);
}
}
// Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
if ($response) {
$response->addHeader('X-Debug-Fallback', 'false');
}
if (empty($session['id']) && empty($session['secret'])) {
if ($response) {
$response->addHeader('X-Debug-Fallback', 'true');
}
$fallback = $request->getHeader('x-fallback-cookies', '');
$fallback = \json_decode($fallback, true);
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
}
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
if (APP_MODE_ADMIN !== $mode) {
if ($project->isEmpty()) {
$user = new Document([]);
} else {
if ($project->getId() === 'console') {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} else {
$user = $dbForProject->getDocument('users', Auth::$unique);
}
}
} else {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
}
if (
$user->isEmpty() // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
) { // Validate user has valid login token
$user = new Document([]);
}
// if (APP_MODE_ADMIN === $mode) {
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
// Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
// } else {
// $user = new Document([]);
// }
// }
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
$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)) {
$user = $dbForProject->getDocument('users', $jwtUserId);
}
$jwtSessionId = $payload['sessionId'] ?? '';
if (!empty($jwtSessionId)) {
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
$user = new Document([]);
}
}
}
$dbForProject->setMetadata('user', $user->getId());
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
App::setResource('project', function ($dbForPlatform, $request, $console) {
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var Utopia\Database\Document $console */
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
if (empty($projectId) || $projectId === 'console') {
return $console;
}
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
return $project;
}, ['dbForPlatform', 'request', 'console']);
App::setResource('session', function (Document $user) {
if ($user->isEmpty()) {
return;
}
$sessions = $user->getAttribute('sessions', []);
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
if (!$sessionId) {
return;
}
foreach ($sessions as $session) {/** @var Document $session */
if ($sessionId === $session->getId()) {
return $session;
}
}
return;
}, ['user']);
App::setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$database
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$dbAdapter = $pools
->get('console')
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$database
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
return $database;
}, ['pools', 'cache']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$configure = (function (Database $database) use ($project, $dsn) {
$database
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
});
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$configure($database);
return $database;
}
$dbAdapter = $pools
->get($dsn->getHost())
->pop()
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$dsn->getHost()] = $database;
$configure($database);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache']);
App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
return $database;
}
$dbAdapter = $pools
->get('logs')
->pop()
->getResource();
$database = new Database(
$dbAdapter,
$cache
);
$database
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant($project->getInternalId());
}
return $database;
};
}, ['pools', 'cache']);
App::setResource('cache', function (Group $pools) {
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
}
return new Cache(new Sharding($adapters));
}, ['pools']);
App::setResource('redis', function () {
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
$port = System::getEnv('_APP_REDIS_PORT', 6379);
$pass = System::getEnv('_APP_REDIS_PASS', '');
$redis = new \Redis();
@$redis->pconnect($host, (int)$port);
if ($pass) {
$redis->auth($pass);
}
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
});
App::setResource('timelimit', function (\Redis $redis) {
return function (string $key, int $limit, int $time) use ($redis) {
return new TimeLimitRedis($key, $limit, $time, $redis);
};
}, ['redis']);
App::setResource('deviceForLocal', function () {
return new Local();
});
App::setResource('deviceForFiles', function ($project) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForFunctions', function ($project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForBuilds', function ($project) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
}, ['project']);
function getDevice(string $root, string $connection = ''): Device
{
$connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', '');
if (!empty($connection)) {
$acl = 'private';
$device = Storage::DEVICE_LOCAL;
$accessKey = '';
$accessSecret = '';
$bucket = '';
$region = '';
$url = System::getEnv('_APP_STORAGE_S3_ENDPOINT', '');
try {
$dsn = new DSN($connection);
$device = $dsn->getScheme();
$accessKey = $dsn->getUser() ?? '';
$accessSecret = $dsn->getPassword() ?? '';
$bucket = $dsn->getPath() ?? '';
$region = $dsn->getParam('region');
} catch (\Throwable $e) {
Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
}
switch ($device) {
case Storage::DEVICE_S3:
return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl, $url);
case STORAGE::DEVICE_DO_SPACES:
$device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
$device->setHttpVersion(S3::HTTP_VERSION_1_1);
return $device;
case Storage::DEVICE_BACKBLAZE:
return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case Storage::DEVICE_LINODE:
return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case Storage::DEVICE_WASABI:
return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl);
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
}
} else {
switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) {
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
case Storage::DEVICE_S3:
$s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
$s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', '');
$s3Region = System::getEnv('_APP_STORAGE_S3_REGION', '');
$s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', '');
$s3Acl = 'private';
$s3EndpointUrl = System::getEnv('_APP_STORAGE_S3_ENDPOINT', '');
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl, $s3EndpointUrl);
case Storage::DEVICE_DO_SPACES:
$doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
$doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
$doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
$doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
$doSpacesAcl = 'private';
$device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
$device->setHttpVersion(S3::HTTP_VERSION_1_1);
return $device;
case Storage::DEVICE_BACKBLAZE:
$backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
$backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
$backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
$backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
$backblazeAcl = 'private';
return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
case Storage::DEVICE_LINODE:
$linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
$linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', '');
$linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', '');
$linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
$linodeAcl = 'private';
return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
case Storage::DEVICE_WASABI:
$wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
$wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', '');
$wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', '');
$wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
$wasabiAcl = 'private';
return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
}
}
}
App::setResource('mode', function ($request) {
/** @var Appwrite\Utopia\Request $request */
/**
* Defines the mode for the request:
* - 'default' => Requests for Client and Server Side
* - 'admin' => Request from the Console on non-console projects
*/
return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
}, ['request']);
App::setResource('geodb', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('geodb');
}, ['register']);
App::setResource('passwordsDictionary', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('passwordsDictionary');
}, ['register']);
App::setResource('servers', function () {
$platforms = Config::getParam('platforms');
$server = $platforms[APP_PLATFORM_SERVER];
$languages = array_map(function ($language) {
return strtolower($language['name']);
}, $server['sdks']);
return $languages;
});
App::setResource('promiseAdapter', function ($register) {
return $register->get('promiseAdapter');
}, ['register']);
App::setResource('schema', function ($utopia, $dbForProject) {
$complexity = function (int $complexity, array $args) {
$queries = Query::parseQueries($args['queries'] ?? []);
$query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null;
$limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
return $complexity * $limit;
};
$attributes = function (int $limit, int $offset) use ($dbForProject) {
$attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
Query::limit($limit),
Query::offset($offset),
]));
return \array_map(function ($attr) {
return $attr->getArrayCopy();
}, $attrs);
};
$urls = [
'list' => function (string $databaseId, string $collectionId, array $args) {
return "/v1/databases/$databaseId/collections/$collectionId/documents";
},
'create' => function (string $databaseId, string $collectionId, array $args) {
return "/v1/databases/$databaseId/collections/$collectionId/documents";
},
'read' => function (string $databaseId, string $collectionId, array $args) {
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
},
'update' => function (string $databaseId, string $collectionId, array $args) {
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
},
'delete' => function (string $databaseId, string $collectionId, array $args) {
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
},
];
$params = [
'list' => function (string $databaseId, string $collectionId, array $args) {
return [ 'queries' => $args['queries']];
},
'create' => function (string $databaseId, string $collectionId, array $args) {
$id = $args['id'] ?? 'unique()';
$permissions = $args['permissions'] ?? null;
unset($args['id']);
unset($args['permissions']);
// Order must be the same as the route params
return [
'databaseId' => $databaseId,
'documentId' => $id,
'collectionId' => $collectionId,
'data' => $args,
'permissions' => $permissions,
];
},
'update' => function (string $databaseId, string $collectionId, array $args) {
$documentId = $args['id'];
$permissions = $args['permissions'] ?? null;
unset($args['id']);
unset($args['permissions']);
// Order must be the same as the route params
return [
'databaseId' => $databaseId,
'collectionId' => $collectionId,
'documentId' => $documentId,
'data' => $args,
'permissions' => $permissions,
];
},
];
return Schema::build(
$utopia,
$complexity,
$attributes,
$urls,
$params,
);
}, ['utopia', 'dbForProject']);
App::setResource('contributors', function () {
$path = 'app/config/contributors.json';
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
return $list;
});
App::setResource('employees', function () {
$path = 'app/config/employees.json';
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
return $list;
});
App::setResource('heroes', function () {
$path = 'app/config/heroes.json';
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
return $list;
});
App::setResource('gitHub', function (Cache $cache) {
return new VcsGitHub($cache);
}, ['cache']);
App::setResource('requestTimestamp', function ($request) {
//TODO: Move this to the Request class itself
$timestampHeader = $request->getHeader('x-appwrite-timestamp');
$requestTimestamp = null;
if (!empty($timestampHeader)) {
try {
$requestTimestamp = new \DateTime($timestampHeader);
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
}
}
return $requestTimestamp;
}, ['request']);
App::setResource('plan', function (array $plan = []) {
return [];
});
App::setResource('smsRates', function () {
return [];
});
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
$teamInternalId = '';
if ($project->getId() !== 'console') {
$teamInternalId = $project->getAttribute('teamInternalId', '');
} else {
$route = $utopia->match($request);
$path = $route->getPath();
if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI();
$pid = explode('/', $uri)[3];
$p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid));
$teamInternalId = $p->getAttribute('teamInternalId', '');
} elseif ($path === '/v1/projects') {
$teamId = $request->getParam('teamId', '');
$team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
return $team;
}
}
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
return $dbForPlatform->findOne('teams', [
Query::equal('$internalId', [$teamInternalId]),
]);
});
return $team;
}, ['project', 'dbForPlatform', 'utopia', 'request']);
App::setResource(
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
App::setResource('previewHostname', function (Request $request) {
if (App::isDevelopment()) {
$host = $request->getQuery('appwrite-hostname') ?? '';
if (!empty($host)) {
return $host;
}
}
return '';
}, ['request']);
App::setResource('apiKey', function (Request $request, Document $project): ?Key {
$key = $request->getHeader('x-appwrite-key');
if (empty($key)) {
return null;
}
return Key::decode($project, $key);
}, ['request', 'project']);
+3 -1
View File
@@ -168,7 +168,7 @@ $image = $this->getParam('image', '');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:5.2.27
image: <?php echo $organization; ?>/console:5.2.53
restart: unless-stopped
networks:
- appwrite
@@ -342,6 +342,8 @@ $image = $this->getParam('image', '');
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_EMAIL_CERTIFICATES
appwrite-worker-databases:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
+5 -1
View File
@@ -112,6 +112,8 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
->setNamespace('_' . $project->getInternalId());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
@@ -173,6 +175,8 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
->setNamespace('_' . $project->getInternalId());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache']);
@@ -198,7 +202,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
+10 -10
View File
@@ -30,7 +30,7 @@
}
},
"require": {
"php": ">=8.0.0",
"php": ">=8.3.0",
"ext-curl": "*",
"ext-imagick": "*",
"ext-mbstring": "*",
@@ -45,13 +45,13 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.16.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.51.*",
"utopia-php/abuse": "0.52.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.54.0",
"utopia-php/audit": "0.55.*",
"utopia-php/cache": "0.12.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.60.*",
"utopia-php/database": "0.61.*",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
@@ -60,12 +60,12 @@
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.16.*",
"utopia-php/migration": "0.6.*",
"utopia-php/migration": "0.8.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.3",
"utopia-php/pools": "0.5.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.7.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.8.*",
"utopia-php/queue": "0.9.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
@@ -99,8 +99,8 @@
"php": "8.3"
},
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": false
"php-http/discovery": true,
"tbachert/spi": true
}
}
}
Generated
+92 -91
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "58ad2e1375ec47d944b96864d4aae63f",
"content-hash": "eb2ac0b0f9f8dc8b5efd47836b08bb44",
"packages": [
{
"name": "adhocore/jwt",
@@ -709,16 +709,16 @@
},
{
"name": "google/protobuf",
"version": "v4.30.0",
"version": "v4.30.1",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6"
"reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/e1d66682f6836aa87820400f0aa07d9eb566feb6",
"reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/f29ba8a30dfd940efb3a8a75dc44446539101f24",
"reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24",
"shasum": ""
},
"require": {
@@ -747,9 +747,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.0"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.1"
},
"time": "2025-03-04T22:54:49+00:00"
"time": "2025-03-13T21:08:17+00:00"
},
{
"name": "jean85/pretty-package-versions",
@@ -3364,16 +3364,16 @@
},
{
"name": "utopia-php/abuse",
"version": "0.51.0",
"version": "0.52.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "661687b03277f1d202a0e8cf9da6e58c97da2b5e"
"reference": "a0d6421e7e5baa3ac02755496dca9fdeaa814b93"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/661687b03277f1d202a0e8cf9da6e58c97da2b5e",
"reference": "661687b03277f1d202a0e8cf9da6e58c97da2b5e",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/a0d6421e7e5baa3ac02755496dca9fdeaa814b93",
"reference": "a0d6421e7e5baa3ac02755496dca9fdeaa814b93",
"shasum": ""
},
"require": {
@@ -3381,7 +3381,7 @@
"ext-pdo": "*",
"ext-redis": "*",
"php": ">=8.0",
"utopia-php/database": "0.60.*"
"utopia-php/database": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3409,9 +3409,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
"source": "https://github.com/utopia-php/abuse/tree/0.51.0"
"source": "https://github.com/utopia-php/abuse/tree/0.52.0"
},
"time": "2025-02-17T11:10:18+00:00"
"time": "2025-03-06T03:48:29+00:00"
},
{
"name": "utopia-php/analytics",
@@ -3461,21 +3461,21 @@
},
{
"name": "utopia-php/audit",
"version": "0.54.0",
"version": "0.55.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "1b0cb8ac6bfbd7703e3f9a753c6ba59ff1c39975"
"reference": "9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/1b0cb8ac6bfbd7703e3f9a753c6ba59ff1c39975",
"reference": "1b0cb8ac6bfbd7703e3f9a753c6ba59ff1c39975",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518",
"reference": "9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/database": "0.60.*"
"utopia-php/database": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3502,9 +3502,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/0.54.0"
"source": "https://github.com/utopia-php/audit/tree/0.55.0"
},
"time": "2025-02-25T07:21:07+00:00"
"time": "2025-03-06T03:47:47+00:00"
},
{
"name": "utopia-php/cache",
@@ -3705,16 +3705,16 @@
},
{
"name": "utopia-php/database",
"version": "0.60.6",
"version": "0.61.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "f3c9aa964b39c6205069f038a26e709a15541406"
"reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/f3c9aa964b39c6205069f038a26e709a15541406",
"reference": "f3c9aa964b39c6205069f038a26e709a15541406",
"url": "https://api.github.com/repos/utopia-php/database/zipball/349fbdf4bc088f7775c7dfb8b80239a617a88436",
"reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436",
"shasum": ""
},
"require": {
@@ -3755,9 +3755,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.60.6"
"source": "https://github.com/utopia-php/database/tree/0.61.2"
},
"time": "2025-03-05T01:23:14+00:00"
"time": "2025-03-15T11:47:42+00:00"
},
{
"name": "utopia-php/domains",
@@ -4159,16 +4159,16 @@
},
{
"name": "utopia-php/migration",
"version": "0.6.20",
"version": "0.8.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "8c9ba52196f50aaef4aa1903f0d8fe0c8d9997ba"
"reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/8c9ba52196f50aaef4aa1903f0d8fe0c8d9997ba",
"reference": "8c9ba52196f50aaef4aa1903f0d8fe0c8d9997ba",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab",
"reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab",
"shasum": ""
},
"require": {
@@ -4176,7 +4176,7 @@
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.1",
"utopia-php/database": "0.60.*",
"utopia-php/database": "0.61.*",
"utopia-php/dsn": "0.2.*",
"utopia-php/framework": "0.33.*",
"utopia-php/storage": "0.18.*"
@@ -4209,9 +4209,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.6.20"
"source": "https://github.com/utopia-php/migration/tree/0.8.1"
},
"time": "2025-02-17T11:02:15+00:00"
"time": "2025-03-18T07:48:08+00:00"
},
{
"name": "utopia-php/mongo",
@@ -4325,16 +4325,16 @@
},
{
"name": "utopia-php/platform",
"version": "0.7.3",
"version": "0.7.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
"reference": "463c2d817c893d7dbb678c2eac7a8291f2710e25"
"reference": "a5b93d8177702ec458c3af9137663133c012b71b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/463c2d817c893d7dbb678c2eac7a8291f2710e25",
"reference": "463c2d817c893d7dbb678c2eac7a8291f2710e25",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/a5b93d8177702ec458c3af9137663133c012b71b",
"reference": "a5b93d8177702ec458c3af9137663133c012b71b",
"shasum": ""
},
"require": {
@@ -4343,7 +4343,7 @@
"php": ">=8.0",
"utopia-php/cli": "0.15.*",
"utopia-php/framework": "0.33.*",
"utopia-php/queue": "0.8.*"
"utopia-php/queue": "0.9.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -4369,31 +4369,32 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/0.7.3"
"source": "https://github.com/utopia-php/platform/tree/0.7.4"
},
"time": "2025-02-04T15:09:00+00:00"
"time": "2025-03-13T13:00:12+00:00"
},
{
"name": "utopia-php/pools",
"version": "0.5.0",
"version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/pools.git",
"reference": "6f716a213a08db95eda1b5dddfa90983c1834817"
"reference": "ad64d45afda08ec8b29e2642a8d18075964d40bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/pools/zipball/6f716a213a08db95eda1b5dddfa90983c1834817",
"reference": "6f716a213a08db95eda1b5dddfa90983c1834817",
"url": "https://api.github.com/repos/utopia-php/pools/zipball/ad64d45afda08ec8b29e2642a8d18075964d40bf",
"reference": "ad64d45afda08ec8b29e2642a8d18075964d40bf",
"shasum": ""
},
"require": {
"php": ">=8.0"
"php": ">=8.3",
"utopia-php/telemetry": "0.1.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
"phpstan/phpstan": "1.8.*",
"phpunit/phpunit": "^9.3"
"laravel/pint": "1.*",
"phpstan/phpstan": "1.*",
"phpunit/phpunit": "11.*"
},
"type": "library",
"autoload": {
@@ -4420,9 +4421,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/pools/issues",
"source": "https://github.com/utopia-php/pools/tree/0.5.0"
"source": "https://github.com/utopia-php/pools/tree/0.7.0"
},
"time": "2024-04-19T11:11:54+00:00"
"time": "2025-03-18T03:55:33+00:00"
},
{
"name": "utopia-php/preloader",
@@ -4479,16 +4480,16 @@
},
{
"name": "utopia-php/queue",
"version": "0.8.6",
"version": "0.9.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "b713b997285c29d120bbcbe3d6e93762d850f87c"
"reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/b713b997285c29d120bbcbe3d6e93762d850f87c",
"reference": "b713b997285c29d120bbcbe3d6e93762d850f87c",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/077075f1d57afa430f76c35ed3bf4616e0eee8e7",
"reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7",
"shasum": ""
},
"require": {
@@ -4538,9 +4539,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.8.6"
"source": "https://github.com/utopia-php/queue/tree/0.9.0"
},
"time": "2025-02-10T03:35:00+00:00"
"time": "2025-03-13T12:22:41+00:00"
},
{
"name": "utopia-php/registry",
@@ -4760,16 +4761,16 @@
},
{
"name": "utopia-php/telemetry",
"version": "0.1.0",
"version": "0.1.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/telemetry.git",
"reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80"
"reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/telemetry/zipball/d35f2f0632f4ee0be63fb7ace6a94a6adda71a80",
"reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80",
"url": "https://api.github.com/repos/utopia-php/telemetry/zipball/437f0021777f0e575dfb9e8a1a081b3aed75e33f",
"reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f",
"shasum": ""
},
"require": {
@@ -4790,7 +4791,7 @@
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\": "src/"
"Utopia\\Telemetry\\": "src/Telemetry"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -4804,22 +4805,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/telemetry/issues",
"source": "https://github.com/utopia-php/telemetry/tree/0.1.0"
"source": "https://github.com/utopia-php/telemetry/tree/0.1.1"
},
"time": "2024-11-13T10:29:53+00:00"
"time": "2025-03-17T11:57:52+00:00"
},
{
"name": "utopia-php/vcs",
"version": "0.9.3",
"version": "0.9.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/vcs.git",
"reference": "865a00c67e81a20938b883f9aa802303790dd3b5"
"reference": "1a8d280b176acc99ea8d9e7364b8767cbb206b4a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/865a00c67e81a20938b883f9aa802303790dd3b5",
"reference": "865a00c67e81a20938b883f9aa802303790dd3b5",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/1a8d280b176acc99ea8d9e7364b8767cbb206b4a",
"reference": "1a8d280b176acc99ea8d9e7364b8767cbb206b4a",
"shasum": ""
},
"require": {
@@ -4854,9 +4855,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/vcs/issues",
"source": "https://github.com/utopia-php/vcs/tree/0.9.3"
"source": "https://github.com/utopia-php/vcs/tree/0.9.4"
},
"time": "2025-02-26T16:33:35+00:00"
"time": "2025-03-13T10:09:45+00:00"
},
{
"name": "utopia-php/websocket",
@@ -5043,16 +5044,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.40.2",
"version": "0.40.9",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "56f09482d9e2f223911277ab887f197402708049"
"reference": "dbb45a5db22cdc3368fe2573c07ba6088f188fa4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/56f09482d9e2f223911277ab887f197402708049",
"reference": "56f09482d9e2f223911277ab887f197402708049",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/dbb45a5db22cdc3368fe2573c07ba6088f188fa4",
"reference": "dbb45a5db22cdc3368fe2573c07ba6088f188fa4",
"shasum": ""
},
"require": {
@@ -5088,9 +5089,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.2"
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.9"
},
"time": "2025-03-06T16:31:03+00:00"
"time": "2025-03-17T18:39:14+00:00"
},
{
"name": "doctrine/annotations",
@@ -5317,16 +5318,16 @@
},
{
"name": "laravel/pint",
"version": "v1.21.0",
"version": "v1.21.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425"
"reference": "370772e7d9e9da087678a0edf2b11b6960e40558"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/531fa0871fbde719c51b12afa3a443b8f4e4b425",
"reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425",
"url": "https://api.github.com/repos/laravel/pint/zipball/370772e7d9e9da087678a0edf2b11b6960e40558",
"reference": "370772e7d9e9da087678a0edf2b11b6960e40558",
"shasum": ""
},
"require": {
@@ -5337,9 +5338,9 @@
"php": "^8.2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.68.5",
"illuminate/view": "^11.42.0",
"larastan/larastan": "^3.0.4",
"friendsofphp/php-cs-fixer": "^3.72.0",
"illuminate/view": "^11.44.2",
"larastan/larastan": "^3.2.0",
"laravel-zero/framework": "^11.36.1",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.3",
@@ -5379,7 +5380,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2025-02-18T03:18:57+00:00"
"time": "2025-03-14T22:31:42+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -5794,16 +5795,16 @@
},
{
"name": "phpbench/phpbench",
"version": "1.4.0",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/phpbench/phpbench.git",
"reference": "4248817222514421cba466bfa7adc7d8932345d4"
"reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/4248817222514421cba466bfa7adc7d8932345d4",
"reference": "4248817222514421cba466bfa7adc7d8932345d4",
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b",
"reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b",
"shasum": ""
},
"require": {
@@ -5880,7 +5881,7 @@
],
"support": {
"issues": "https://github.com/phpbench/phpbench/issues",
"source": "https://github.com/phpbench/phpbench/tree/1.4.0"
"source": "https://github.com/phpbench/phpbench/tree/1.4.1"
},
"funding": [
{
@@ -5888,7 +5889,7 @@
"type": "github"
}
],
"time": "2025-01-26T19:54:45+00:00"
"time": "2025-03-12T08:01:40+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -8406,7 +8407,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.0.0",
"php": ">=8.3.0",
"ext-curl": "*",
"ext-imagick": "*",
"ext-mbstring": "*",
+1 -1
View File
@@ -201,7 +201,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:5.2.27
image: appwrite/console:5.2.53
restart: unless-stopped
networks:
- appwrite
+1 -1
View File
@@ -1 +1 @@
Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection) API or directly from your database console.
Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection) API or directly from your database console.
@@ -0,0 +1 @@
Bulk delete documents using queries, if no queries are passed then all documents are deleted.
@@ -0,0 +1 @@
Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be updated.
+4 -4
View File
@@ -59,7 +59,7 @@ My Awesome Function
You can now deploy this function using
```sh
$ appwrite deploy function
$ appwrite push function
? Which functions would you like to deploy? My Awesome Function (61d1a4c81dfcd95bc834)
Info Deploying function My Awesome Function ( 61d1a4c81dfcd95bc834 )
@@ -73,7 +73,7 @@ Your function has now been deployed on your Appwrite server! As soon as the buil
Similarly, you can deploy all your collections to your Appwrite server using
```sh
appwrite deploy collections
appwrite push collections
```
> ### Note
@@ -98,7 +98,7 @@ $ appwrite users list
To create a document you can use the following command
```sh
$ appwrite database createDocument --collectionId <ID> --documentId 'unique()' --data '{ "Name": "Iron Man" }' --permissions 'read("any")' 'read("team:abc")'
$ appwrite databases create-document --database-id <DATABASE_ID> --collection-id <COLLECTION_ID> --document-id "unique()" --data '{"name": "Walter O Brein"}' --permissions 'read("any")' 'read("team:abc")'
```
### Some Gotchas
@@ -140,4 +140,4 @@ The Appwrite CLI can also work in a CI environment. The initialisation of the CL
```sh
appwrite client --endpoint http://localhost/v1 --projectId <PROJECT_ID> --key <API KEY>
```
```
+12
View File
@@ -1,3 +1,15 @@
## 14.0.0
* Breaking changes:
* Changed the typing of `AppwriteException`'s response parameter from a `dynamic` object to an optional string (`?String`).
## 13.0.0
* Fixed realtime pong response.
* Fixed issues with `chunkedUpload` method.
* Fixed type mismatch bug where `List<dynamic>` was incorrectly causing runtime type errors.
* Updated return type of `updateMfaChallenge()` from raw data to properly typed `models.Session` object.
## 12.0.0
* Support for Appwrite 1.6
+10
View File
@@ -1,3 +1,13 @@
## 15.0.0
* Breaking changes:
* Changed the typing of `AppwriteException`'s response parameter from a `dynamic` object to an optional string (`?String`).
## 14.0.0
* Fixed realtime pong response.
* Fixed issues with `chunkedUpload` method.
## 13.0.0
* Fixed realtime reconnection issues
+13 -7
View File
@@ -85,13 +85,19 @@ App::post('/v1/account/create')
```php
App::post('/v1/account/jwt')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createJWT')
->label('sdk.description', '/docs/references/account/create-jwt.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_JWT)
->label('sdk', new Method(
namespace: 'account',
name: 'createJWT',
description: '/docs/references/account/create-jwt.md',
auth: [],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_JWT,
)
],
responseType: ResponseType::JSON,
))
```
#### Cache
+19 -5
View File
@@ -387,6 +387,8 @@ class Deletes extends Action
$query = [
Query::lessThan('accessedAt', $datetime),
Query::orderDesc('accessedAt'),
Query::orderDesc('$internalId'),
];
$this->deleteByGroup(
@@ -414,22 +416,26 @@ class Deletes extends Action
*/
private function deleteUsageStats(Document $project, callable $getProjectDB, callable $getLogsDB, string $hourlyUsageRetentionDatetime): void
{
/** @var \Utopia\Database\Database $dbForProject*/
/** @var Database $dbForProject*/
$dbForProject = $getProjectDB($project);
// Delete Usage stats from projectDB
$this->deleteByGroup('stats', [
Query::lessThan('time', $hourlyUsageRetentionDatetime),
Query::orderDesc('time'),
Query::orderDesc('$internalId'),
Query::equal('period', ['1h']),
], $dbForProject);
if ($project->getId() !== 'console') {
/** @var \Utopia\Database\Database $dbForLogs*/
/** @var Database $dbForLogs*/
$dbForLogs = call_user_func($getLogsDB, $project);
// Delete Usage stats from logsDB
$this->deleteByGroup('stats', [
Query::lessThan('time', $hourlyUsageRetentionDatetime),
Query::orderDesc('time'),
Query::orderDesc('$internalId'),
Query::equal('period', ['1h']),
], $dbForLogs);
}
@@ -688,9 +694,12 @@ class Deletes extends Action
private function deleteExecutionLogs(Document $project, callable $getProjectDB, string $datetime): void
{
$dbForProject = $getProjectDB($project);
// Delete Executions
$this->deleteByGroup('executions', [
Query::lessThan('$createdAt', $datetime)
Query::lessThan('$createdAt', $datetime),
Query::orderDesc('$createdAt'),
Query::orderDesc('$internalId'),
], $dbForProject);
}
@@ -708,7 +717,9 @@ class Deletes extends Action
// Delete Sessions
$this->deleteByGroup('sessions', [
Query::lessThan('$createdAt', $expired)
Query::lessThan('$createdAt', $expired),
Query::orderDesc('$createdAt'),
Query::orderDesc('$internalId'),
], $dbForProject);
}
@@ -722,7 +733,9 @@ class Deletes extends Action
{
// Delete Dead Realtime Logs
$this->deleteByGroup('realtime', [
Query::lessThan('timestamp', $datetime)
Query::lessThan('timestamp', $datetime),
Query::orderDesc('timestamp'),
Query::orderDesc('$internalId'),
], $dbForPlatform);
}
@@ -742,6 +755,7 @@ class Deletes extends Action
$this->deleteByGroup(Audit::COLLECTION, [
Query::lessThan('time', $auditRetention),
Query::orderDesc('time'),
Query::orderDesc('$internalId'),
], $dbForProject);
} catch (DatabaseException $e) {
Console::error('Failed to delete audit logs for project ' . $projectId . ': ' . $e->getMessage());
@@ -34,6 +34,9 @@ class Migrations extends Action
protected Document $project;
/**
* @var callable
*/
protected $logError;
public static function getName(): string
@@ -114,6 +114,13 @@ class StatsResources extends Action
$keys = $dbForPlatform->count('keys', [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
$domains = $dbForPlatform->count('rules', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('owner', ['']),
]);
$databases = $dbForProject->count('databases');
$buckets = $dbForProject->count('buckets');
$users = $dbForProject->count('users');
@@ -162,6 +169,7 @@ class StatsResources extends Action
METRIC_PROVIDERS => $providers,
METRIC_TOPICS => $topics,
METRIC_KEYS => $keys,
METRIC_DOMAINS => $domains,
METRIC_TARGETS => $targets,
str_replace('{providerType}', MESSAGE_TYPE_EMAIL, METRIC_PROVIDER_TYPE_TARGETS) => $emailTargets,
str_replace('{providerType}', MESSAGE_TYPE_PUSH, METRIC_PROVIDER_TYPE_TARGETS) => $pushTargets,
+17 -10
View File
@@ -131,10 +131,8 @@ class OpenAPI3 extends Format
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
$additionalMethods = $sdk;
$sdk = $mainSdk;
$sdk = $sdk[0];
}
/**
@@ -202,14 +200,16 @@ class OpenAPI3 extends Format
if (!empty($additionalMethods)) {
$temp['x-appwrite']['additional-methods'] = [];
$temp['x-appwrite']['methods'] = [];
foreach ($additionalMethods as $method) {
/** @var \Appwrite\SDK\Method $method */
$desc = $method->getDescriptionFilePath();
$additionalMethod = [
'name' => $method->getMethodName(),
'parameters' => [],
'required' => [],
'responses' => []
'responses' => [],
'description' => ($desc) ? \file_get_contents($desc) : '',
];
foreach ($method->getParameters() as $name => $param) {
@@ -222,13 +222,20 @@ class OpenAPI3 extends Format
foreach ($method->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/components/schemas/' . $response->getModel()
];
if (\is_array($response->getModel())) {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => \array_map(fn ($m) => '#/components/schemas/' . $m, $response->getModel())
];
} else {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/components/schemas/' . $response->getModel()
];
}
}
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
$temp['x-appwrite']['methods'][] = $additionalMethod;
}
}
+17 -10
View File
@@ -130,10 +130,8 @@ class Swagger2 extends Format
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
$additionalMethods = $sdk;
$sdk = $mainSdk;
$sdk = $sdk[0];
}
$consumes = [$sdk->getRequestType()];
@@ -203,15 +201,17 @@ class Swagger2 extends Format
}
if (!empty($additionalMethods)) {
$temp['x-appwrite']['additional-methods'] = [];
$temp['x-appwrite']['methods'] = [];
foreach ($additionalMethods as $method) {
/** @var \Appwrite\SDK\Method $method */
$desc = $method->getDescriptionFilePath();
$additionalMethod = [
'name' => $method->getMethodName(),
'parameters' => [],
'required' => [],
'responses' => [],
'description' => $method->getDescription(),
'description' => ($desc) ? \file_get_contents($desc) : '',
];
foreach ($method->getParameters() as $name => $param) {
@@ -224,13 +224,20 @@ class Swagger2 extends Format
foreach ($method->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/definitions/' . $response->getModel()
];
if (\is_array($response->getModel())) {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => \array_map(fn ($m) => '#/definitions/' . $m, $response->getModel())
];
} else {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/definitions/' . $response->getModel()
];
}
}
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
$temp['x-appwrite']['methods'][] = $additionalMethod;
}
}
@@ -0,0 +1,127 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { provisionProject, provisionDatabase, cleanup, unique } from "./utils.js";
const millionRecords = 1_000_000;
const batchSize = 10_000;
const numBatches = millionRecords / batchSize;
export function setup() {
const resources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'test@test.com',
password: 'password123',
name: 'Test User',
projectName: 'Large Document Creation Test'
});
const { databaseId, collectionId } = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: resources.apiHeaders
});
// Wait to ensure that provisioning is complete
sleep(5);
// Create an index for the collection
const index = {
key: "name",
type: "fulltext",
orders: ["ASC"],
attributes: ["name", "email"]
};
const indexRes = http.post(`http://localhost/v1/databases/${databaseId}/collections/${collectionId}/indexes`,
JSON.stringify(index), {
headers: resources.apiHeaders
});
console.log(indexRes.status);
check(indexRes, {
"status is 202": (r) => r.status === 202,
});
console.log(`----- Inserting ${millionRecords} documents in ${numBatches} batches of ${batchSize} -----`);
const timeStart = new Date();
const requests = [];
for (let i = 0; i < numBatches; i++) {
const docs = Array.from({ length: batchSize }, () => ({
$id: unique(),
name: "bulk_document",
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100
}));
requests.push({
method: "POST",
url: `http://localhost/v1/databases/${databaseId}/collections/${collectionId}/documents`,
body: JSON.stringify({ documents: docs }),
params: {
headers: resources.apiHeaders,
timeout: '300s'
}
});
}
const responses = http.batch(requests);
responses.forEach((res, index) => {
if (res.status !== 201) {
throw new Error(`Batch ${index + 1} failed with status ${res.status}`);
}
});
const timeEnd = new Date();
const timeTaken = timeEnd - timeStart;
console.log(`Created 1 million documents in ${timeTaken} milliseconds`);
return {
databaseId,
collectionId,
apiHeaders: resources.apiHeaders,
resources
};
}
export default function (data) {
const docs = Array.from({ length: 10000 }, () => ({
$id: unique(),
name: "performance_document",
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100
}));
const payload = JSON.stringify({ documents: docs });
const res = http.post(
`http://localhost/v1/databases/${data.databaseId}/collections/${data.collectionId}/documents`,
payload,
{
headers: data.apiHeaders,
timeout: '300s'
}
);
check(res, {
"status is 201": (r) => r.status === 201
});
sleep(1);
}
export function teardown(data) {
cleanup(data.resources);
}
export const options = {
scenarios: {
large_document_creation: {
executor: 'per-vu-iterations',
vus: 1,
iterations: 20,
exec: 'default'
}
}
};
@@ -0,0 +1,82 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { provisionProject, provisionDatabase, cleanup, unique } from "./utils.js";
const amount = 10_000;
export function setup() {
const resources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'test@test.com',
password: 'password123',
name: 'Test User',
projectName: 'Bulk Operations Test'
});
const { databaseId, collectionId } = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: resources.apiHeaders
});
sleep(3); // Await Attributes to be provisioned
console.log(`----- Amount of documents: ${amount} -----`);
return {
databaseId,
collectionId,
apiHeaders: resources.apiHeaders,
resources
};
}
export function teardown(data) {
cleanup(data.resources);
}
let documents = Array(amount).fill({
$id: "unique()",
name: "asd",
});
documents = documents.map((document) => {
return {
...document,
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100,
};
});
export default function (data) {
const payload = JSON.stringify({
documents,
});
const res = http.post(
`http://localhost/v1/databases/${data.databaseId}/collections/${data.collectionId}/documents`,
payload,
{
headers: data.apiHeaders
}
);
check(res, {
"status is 201": (r) => r.status === 201,
});
return {
resources: data.resources
};
}
export const options = {
scenarios: {
bulk_create: {
executor: 'per-vu-iterations',
vus: 1,
iterations: 20,
exec: 'default'
}
}
};
@@ -0,0 +1,144 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { Trend } from "k6/metrics";
import { provisionProject, provisionDatabase, cleanup, unique } from "./utils.js";
// Custom Trend metric for light response time tracking
export const lightResponseTime = new Trend("light_response_time", true);
const BULK_AMOUNT = 100_000; // Heavy operation amount
const LIGHT_AMOUNT = 10; // Light operation amount
export function setup() {
// Set up two separate projects - one for bulk operations (noisy neighbor) and one for light operations
const heavyResources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'heavy@test.com',
password: 'password123',
name: 'Heavy User',
projectName: 'Noisy Neighbor - Heavy'
});
const lightResources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'light@test.com',
password: 'password123',
name: 'Light User',
projectName: 'Noisy Neighbor - Light'
});
// Set up databases for both projects
const heavy = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: heavyResources.apiHeaders
});
const light = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: lightResources.apiHeaders
});
sleep(3); // Await Attributes to be provisioned
console.log(`----- Heavy operations: ${BULK_AMOUNT} documents | Light operations: ${LIGHT_AMOUNT} document -----`);
return {
heavy: {
databaseId: heavy.databaseId,
collectionId: heavy.collectionId,
apiHeaders: heavyResources.apiHeaders,
resources: heavyResources
},
light: {
databaseId: light.databaseId,
collectionId: light.collectionId,
apiHeaders: lightResources.apiHeaders,
resources: lightResources
}
};
}
export function teardown(data) {
cleanup(data.heavy.resources);
cleanup(data.light.resources);
}
// Create document payloads
function createDocuments(amount) {
let documents = Array(amount).fill({
$id: "unique()",
name: "test",
});
return documents.map((document) => ({
...document,
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100,
}));
}
// Heavy operation function
export function heavy(data) {
const documents = createDocuments(BULK_AMOUNT);
const payload = JSON.stringify({ documents });
const res = http.post(
`http://localhost/v1/databases/${data.heavy.databaseId}/collections/${data.heavy.collectionId}/documents`,
payload,
{
headers: data.heavy.apiHeaders
}
);
check(res, {
"heavy operation status is 201": (r) => r.status === 201,
});
}
// Light operation function
export function light(data) {
const documents = createDocuments(LIGHT_AMOUNT);
const payload = JSON.stringify({ documents });
const startTime = new Date();
const res = http.post(
`http://localhost/v1/databases/${data.light.databaseId}/collections/${data.light.collectionId}/documents`,
payload,
{
headers: data.light.apiHeaders
}
);
const duration = new Date() - startTime;
// Record the light operation response time using the custom Trend metric
lightResponseTime.add(duration);
check(res, {
"light operation status is 201": (r) => r.status === 201,
});
}
export const options = {
scenarios: {
// Heavy bulk operations running continuously
heavy_load: {
executor: 'constant-vus',
vus: 5,
duration: '30s',
exec: 'heavy'
},
// Light operations to measure impact
light_operations: {
executor: 'constant-arrival-rate',
rate: 5,
timeUnit: '1s',
duration: '30s',
preAllocatedVUs: 10,
exec: 'light'
}
},
thresholds: {
http_req_duration: ['p(95)<2000'], // 95% of requests should complete within 2s
}
};
+336
View File
@@ -0,0 +1,336 @@
import http from 'k6/http';
import { check } from 'k6';
/**
* @typedef {Object} AuthHeaders
* @property {string} 'Content-Type' - Content type header
* @property {string} 'Cookie' - Session cookie
* @property {string} 'X-Appwrite-Project' - Project ID header
*/
/**
* @typedef {Object} ApiHeaders
* @property {string} 'Content-Type' - Content type header
* @property {string} 'X-Appwrite-Project' - Project ID header
* @property {string} 'X-Appwrite-Key' - API key header
*/
/**
* @typedef {Object} ProvisionedResources
* @property {string} userId - The ID of the created user
* @property {string} teamId - The ID of the created team
* @property {string} projectId - The ID of the created project
* @property {string} cookies - Session cookies for authentication
* @property {AuthHeaders} headers - Headers for cookie-based authentication
* @property {string} apiKey - The API key secret
* @property {ApiHeaders} apiHeaders - Headers for API key authentication
*/
function assert(response, checkName, condition) {
const result = check(response, {
[checkName]: condition
});
if (!result) {
console.error(`Assertion failed: ${checkName}`);
console.error(`Response status: ${response.status}`);
console.error(`Response body: ${response.body}`);
throw new Error(`Assertion failed: ${checkName}`);
}
}
/**
* Provisions an Appwrite project setup including:
* - Account creation
* - Session creation
* - Team creation
* - Project creation
* - API Key creation
*
* @param {Object} config Configuration object
* @param {string} config.endpoint Base endpoint URL (e.g., 'http://localhost:80/v1')
* @param {string} config.email Email for account creation
* @param {string} config.password Password for account creation
* @param {string} config.name Name for account creation
* @param {string} config.projectName Name for the project
* @returns {ProvisionedResources} Object containing all created resource IDs and session information
*/
export function provisionProject(config) {
const {
endpoint,
email,
password,
name,
projectName,
} = config;
// Step 1: Create Account
const accountResponse = http.post(`${endpoint}/account`, JSON.stringify({
userId: 'unique()',
email,
password,
name
}), {
headers: {
'Content-Type': 'application/json',
}
});
assert(accountResponse, 'account created successfully', (r) => r.status === 201 || r.status === 409);
const userId = accountResponse.json('$id');
// Step 2: Create Session
const sessionResponse = http.post(`${endpoint}/account/sessions/email`, JSON.stringify({
email,
password
}), {
headers: {
'Content-Type': 'application/json',
}
});
assert(sessionResponse, 'session created successfully', (r) => r.status === 201);
// Keep manual control of the cookies to allow for simultaneous requests
const jar = http.cookieJar();
jar.clear(`${endpoint}`);
// Extract cookies for subsequent requests
const cookies = sessionResponse.headers['Set-Cookie'];
// Common headers for authenticated requests
const authHeaders = {
'Content-Type': 'application/json',
'Cookie': cookies
};
// Step 3: Create Team
const teamResponse = http.post(`${endpoint}/teams`, JSON.stringify({
teamId: 'unique()',
name: `${projectName} Team`
}), {
headers: authHeaders
});
assert(teamResponse, 'team created successfully', (r) => r.status === 201);
const teamId = teamResponse.json('$id');
// Step 4: Create Project
const projectResponse = http.post(`${endpoint}/projects`, JSON.stringify({
projectId: 'unique()',
name: projectName,
teamId: teamId
}), {
headers: authHeaders
});
assert(projectResponse, 'project created successfully', (r) => r.status === 201);
const projectId = projectResponse.json('$id');
// Step 5: Create API Key
const apiKeyResponse = http.post(`${endpoint}/projects/${projectId}/keys`, JSON.stringify({
name: 'Test API Key',
scopes: SCOPES, // All permissions
}), {
headers: authHeaders
});
assert(apiKeyResponse, 'api key created successfully', (r) => r.status === 201);
const apiKey = apiKeyResponse.json('secret');
// Create a new headers object for API key authentication
const apiHeaders = {
'Content-Type': 'application/json',
'X-Appwrite-Project': projectId,
'X-Appwrite-Key': apiKey
};
// Return all created resources and session info
return {
endpoint,
userId,
teamId,
projectId,
cookies,
headers: authHeaders,
apiKey,
apiHeaders
};
}
/**
* Example usage:
*
* const config = {
* endpoint: 'http://localhost:80/v1',
* email: 'test@example.com',
* password: 'complex-password',
* name: 'Test User',
* projectName: 'Test Project'
* };
*
* const resources = provisionProject(config);
*/
const SCOPES = [
"sessions.write",
"users.read",
"users.write",
"teams.read",
"teams.write",
"databases.read",
"databases.write",
"collections.read",
"collections.write",
"attributes.read",
"attributes.write",
"indexes.read",
"indexes.write",
"documents.read",
"documents.write",
"files.read",
"files.write",
"buckets.read",
"buckets.write",
"functions.read",
"functions.write",
"execution.read",
"execution.write",
"targets.read",
"targets.write",
"providers.read",
"providers.write",
"messages.read",
"messages.write",
"topics.read",
"topics.write",
"subscribers.read",
"subscribers.write",
"locale.read",
"avatars.read",
"health.read",
"migrations.read",
"migrations.write"
]
export function provisionDatabase(config) {
const {
endpoint,
apiHeaders
} = config;
// Create database
const databaseResponse = http.post(
`${endpoint}/databases`,
JSON.stringify({
databaseId: 'unique()',
name: 'Bulk Test DB'
}),
{ headers: apiHeaders }
);
assert(databaseResponse, 'database created successfully', (r) => r.status === 201);
const databaseId = databaseResponse.json('$id');
// Create collection
const collectionResponse = http.post(
`${endpoint}/databases/${databaseId}/collections`,
JSON.stringify({
collectionId: 'unique()',
name: 'Bulk Test Collection',
permissions: ['read("any")', 'write("any")'],
documentSecurity: false
}),
{ headers: apiHeaders }
);
assert(collectionResponse, 'collection created successfully', (r) => r.status === 201);
const collectionId = collectionResponse.json('$id');
// Create name attribute
const nameAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/string`,
JSON.stringify({
key: 'name',
size: 100,
required: false,
default: null,
array: false,
encrypt: false
}),
{ headers: apiHeaders }
);
assert(nameAttributeResponse, 'name attribute created successfully', (r) => r.status === 202);
// Create age attribute
const ageAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/integer`,
JSON.stringify({
key: 'age',
required: false,
}),
{ headers: apiHeaders }
);
assert(ageAttributeResponse, 'age attribute created successfully', (r) => r.status === 202);
// Create email attribute
const emailAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/email`,
JSON.stringify({
key: 'email',
required: false,
}),
{ headers: apiHeaders }
);
assert(emailAttributeResponse, 'email attribute created successfully', (r) => r.status === 202);
// Create height attribute
const heightAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/float`,
JSON.stringify({
key: 'height',
required: false,
}),
{ headers: apiHeaders }
);
assert(heightAttributeResponse, 'height attribute created successfully', (r) => r.status === 202);
return {
databaseId,
collectionId
};
}
export function cleanup(config) {
const {
endpoint,
teamId,
headers
} = config;
// Delete Organization
const organizationResponse = http.del(
`${endpoint}/teams/${teamId}`,
null,
{
headers
}
);
assert(organizationResponse, 'organization deleted successfully', (r) => r.status === 204);
}
export function unique() {
const timestamp = Date.now().toString(36);
const randomPart = Math.random().toString(36).substring(2, 15);
return `${timestamp}-${randomPart}`;
}
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,7 @@ use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
class DatabasesCustomClientTest extends Scope
{
@@ -890,4 +891,623 @@ class DatabasesCustomClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
}
public function testBulkCreatePermissions(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Create'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Bulk Create',
'documentSecurity' => false,
'permissions' => [
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
];
// Await attribute
$numberAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['$id'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'number',
'required' => true,
]);
$this->assertEquals(202, $numberAttribute['headers']['status-code']);
sleep(2);
// TEST FAIL - Can't create document with missing collection level permissions
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
],
[
'$id' => ID::unique(),
'number' => 2,
],
[
'$id' => ID::unique(),
'number' => 3,
],
],
]);
$this->assertEquals(401, $response['headers']['status-code']);
$this->assertEquals('The current user is not authorized to perform the requested action.', $response['body']['message']);
// TEST FAIL - Can't create document with missing document level permissions
$collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Bulk Creates Perms',
'documentSecurity' => true
]);
$this->assertEquals(200, $collection['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
],
[
'$id' => ID::unique(),
'number' => 2,
],
[
'$id' => ID::unique(),
'number' => 3,
],
],
]);
$this->assertEquals(401, $response['headers']['status-code']);
$this->assertEquals('The current user is not authorized to perform the requested action.', $response['body']['message']);
// TEST FAIL - Can't create document with permissions that aren't our own.
$collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Bulk Creates Perms',
'documentSecurity' => true,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(200, $collection['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
'$permissions' => [
Permission::create(Role::user('aaaaaa')),
Permission::read(Role::user('aaaaaa')),
Permission::update(Role::user('aaaaaa')),
Permission::delete(Role::user('aaaaaa')),
]
],
[
'$id' => ID::unique(),
'number' => 2,
'$permissions' => [
Permission::create(Role::user('aaaaaa')),
Permission::read(Role::user('aaaaaa')),
Permission::update(Role::user('aaaaaa')),
Permission::delete(Role::user('aaaaaa')),
]
],
[
'$id' => ID::unique(),
'number' => 3,
'$permissions' => [
Permission::create(Role::user('aaaaaa')),
Permission::read(Role::user('aaaaaa')),
Permission::update(Role::user('aaaaaa')),
Permission::delete(Role::user('aaaaaa')),
]
],
],
]);
$this->assertEquals(401, $response['headers']['status-code']);
// TEST SUCCESS - Can create document with our own permissions.
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
'$permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
],
[
'$id' => ID::unique(),
'number' => 2,
'$permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
],
[
'$id' => ID::unique(),
'number' => 3,
'$permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
],
],
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertCount(3, $response['body']['documents']);
}
// Bulk Updates
public function testBulkUpdatesPermissions(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Update Perms'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Bulk Updates Perms',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id']))
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
];
// Await attribute
$numberAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['$id'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'number',
'required' => true,
]);
$this->assertEquals(202, $numberAttribute['headers']['status-code']);
sleep(2);
// TEST: Update all documents with invalid collection level permissions
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 100
]
]);
$this->assertEquals(401, $response['headers']['status-code']);
$collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Bulk Updates Perms',
'documentSecurity' => true
]);
$this->assertEquals(200, $collection['headers']['status-code']);
// TEST: Make sure we can update only documents we have permissions for
for ($i = 0; $i < 6; $i++) {
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'number' => $i,
],
'permissions' => [
Permission::update(Role::user($this->getUser()['$id']))
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
}
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'documentId' => ID::unique(),
'data' => [
'number' => 6,
],
'permissions' => [
Permission::update(Role::user('user2')),
Permission::read(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 100
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(6, $response['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'queries' => [Query::notEqual('number', 100)->toString()]
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(1, $documents['body']['total']);
}
public function testBulkUpdatesRelationship()
{
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Updates'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection1',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection1['headers']['status-code']);
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection2',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection1['headers']['status-code']);
$collection1 = $collection1['body']['$id'];
$collection2 = $collection2['body']['$id'];
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2,
'type' => Database::RELATION_ONE_TO_MANY,
'key' => 'collection2',
'onDelete' => Database::RELATION_MUTATE_RESTRICT,
'twoWay' => true,
'twoWayKey' => 'collection1',
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection2 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
sleep(3);
$document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'name' => 'Document 1',
'collection2' => [
[
'name' => 'Document 2',
],
],
],
]);
$this->assertEquals(201, $document1['headers']['status-code']);
$document1 = $document1['body']['$id'];
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'data' => [
'name' => 'Document 1 Updated',
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['documents']);
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection2 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$document2 = $response['body']['documents'][0];
$this->assertEquals('Document 2', $document2['name']);
$this->assertEquals('Document 1 Updated', $document2['collection1']['name']);
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collection2 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'data' => [
'name' => 'Document 2 Updated',
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents/' . $document1, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('Document 1 Updated', $response['body']['name']);
$document2 = $response['body']['collection2'][0];
$this->assertEquals('Document 2 Updated', $document2['name']);
}
public function testBulkDeletesPermissions(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Deletes Perms'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Bulk Deletes Perms',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id']))
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
];
// Await attribute
$numberAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['$id'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'number',
'required' => true,
]);
$this->assertEquals(202, $numberAttribute['headers']['status-code']);
sleep(2);
// TEST: Delete all documents with invalid collection level permissions
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(401, $response['headers']['status-code']);
$collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Bulk Deletes Perms',
'documentSecurity' => true
]);
$this->assertEquals(200, $collection['headers']['status-code']);
// TEST: Make sure we can delete only documents we have permissions for
for ($i = 0; $i < 6; $i++) {
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'number' => $i,
],
'permissions' => [
Permission::delete(Role::user($this->getUser()['$id']))
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
}
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'documentId' => ID::unique(),
'data' => [
'number' => 6,
],
'permissions' => [
Permission::delete(Role::user('user2'))
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(6, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(1, $documents['body']['total']);
}
}
+2 -2
View File
@@ -228,8 +228,8 @@ trait LocaleBase
* Test for SUCCESS
*/
$languages = require(__DIR__ . '/../../../../app/config/locale/codes.php');
$defaultCountries = require(__DIR__ . '/../../../../app/config/locale/countries.php');
$defaultContinents = require(__DIR__ . '/../../../../app/config/locale/continents.php');
$defaultCountries = array_keys(require(__DIR__ . '/../../../../app/config/locale/countries.php'));
$defaultContinents = array_keys(require(__DIR__ . '/../../../../app/config/locale/continents.php'));
foreach ($languages as $lang) {
$response = $this->client->call(Client::METHOD_GET, '/locale/countries', [
@@ -674,6 +674,7 @@ class RealtimeCustomClientTest extends Scope
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = $this->getProject()['$id'];
$documentIds = [];
$client = $this->getWebsocket(['documents', 'collections'], [
'origin' => 'http://localhost',
@@ -763,6 +764,7 @@ class RealtimeCustomClientTest extends Scope
$response = json_decode($client->receive(), true);
$documentId = $document['body']['$id'];
$documentIds[] = $documentId;
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);