Compare commits

..
Author SHA1 Message Date
Darshan 2d6122a4bf regen: specs. 2025-12-30 18:53:57 +05:30
Darshan ecbdceb407 add: admin scopes for console sdk. 2025-12-30 18:52:53 +05:30
ArnabChatterjee20kandGitHub c7a3c42225 Merge pull request #11044 from appwrite/docs-db-cloud-compat
Sync 1.8.x
2025-12-30 17:53:11 +05:30
Darshan 4cdc8a97a0 lint. 2025-12-30 17:51:47 +05:30
Darshan cf0ea48fc8 Merge branch 'documents-db-api-for-cloud' into docs-db-cloud-compat 2025-12-30 17:50:47 +05:30
Darshan 3771da5353 remove: duplicate constant. 2025-12-30 17:44:56 +05:30
Darshan abd2b7681b remove: duplicate constant. 2025-12-30 17:42:46 +05:30
Darshan 7f35419636 Merge branch '1.8.x' into 'documents-db-api-for-cloud'. 2025-12-30 17:33:20 +05:30
ArnabChatterjee20k d33b91b25c Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api-for-cloud 2025-12-13 22:41:41 +05:30
ArnabChatterjee20k b34a553c3b added new files for cloud compat 2025-12-13 19:37:32 +05:30
ArnabChatterjee20k 5965599eaf updated dependencies 2025-12-13 19:09:54 +05:30
ArnabChatterjee20k a2039c0b76 added deletion test for multidb 2025-12-09 18:34:25 +05:30
ArnabChatterjee20k c549ddd149 updated project deletion worker to delete per database 2025-12-09 18:27:10 +05:30
ArnabChatterjee20k de88ec4065 updated missing skip metrics and tablesdb tests 2025-12-08 20:30:37 +05:30
ArnabChatterjee20k ec63dc22d4 updated controllers 2025-12-08 19:09:54 +05:30
ArnabChatterjee20k 7c7b98399c linting 2025-12-08 17:51:01 +05:30
ArnabChatterjee20k 9d679e726e updated composer lock 2025-12-08 17:46:00 +05:30
ArnabChatterjee20k 070ec03c0c Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-12-08 17:31:04 +05:30
ArnabChatterjee20k be89224c36 updated console client test 2025-12-05 19:14:29 +05:30
ArnabChatterjee20k 8edd3c87cb added documentsdb total reads and writes in the project response 2025-12-05 19:09:35 +05:30
ArnabChatterjee20k 9ac3cd73a8 Refactor database metrics handling for DocumentsDB
- Introduced new methods to retrieve metrics for write and read operations based on database type.
- Updated bulk delete, update, upsert, and create actions to use the new metric methods.
- Enhanced document retrieval and update actions to utilize the new metric structure.
- Added support for DocumentsDB metrics in usage statistics and response models.
- Created new response models for DocumentsDB usage and updated existing models to accommodate new metrics.
- Adjusted migration and stats resources to include DocumentsDB metrics.
- Updated tests to validate the new metrics and ensure correct functionality for DocumentsDB usage.
2025-12-05 18:58:13 +05:30
ArnabChatterjee20k 203170c36d updated database and collection stats 2025-12-04 15:57:18 +05:30
ArnabChatterjee20k 6f5bfdb726 updated stats 2025-12-03 20:18:53 +05:30
b499857871 Update src/Appwrite/Utopia/Database/Validator/Queries/Base.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-12-03 19:12:56 +05:30
1388b0ca21 Update app/config/variables.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2025-12-03 19:12:38 +05:30
ArnabChatterjee20k af095bdf4a updated stats 2025-12-03 19:11:46 +05:30
ArnabChatterjee20k 16f49d13c8 updated composer lock 2025-12-02 18:36:29 +05:30
ArnabChatterjee20k e72d7979e0 updated composer lock 2025-12-02 18:31:44 +05:30
ArnabChatterjee20k 7ad6bb048c merged 1.8.x 2025-12-02 16:35:40 +05:30
ArnabChatterjee20k 3db22fd2db fixed enum route 2025-11-21 20:03:44 +05:30
ArnabChatterjee20k a423067b36 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-11-21 19:53:52 +05:30
ArnabChatterjee20k 33aab0357f updated tests 2025-11-21 19:52:28 +05:30
ArnabChatterjee20k 34e9c69a62 updated tests condition 2025-11-21 19:11:26 +05:30
ArnabChatterjee20k b658cddc71 updated get project dsn to be of the project db type(dedicate, shared) so that databasesdb and projectdb are not in conflict 2025-11-21 17:22:19 +05:30
ArnabChatterjee20k 2f1e47e35a updated migrations 2025-11-20 18:56:14 +05:30
ArnabChatterjee20k 1004435e9e skipped operator tests for mongodb 2025-11-20 14:07:05 +05:30
ArnabChatterjee20k a9e749a81f updated migration service extractor for csv import/export 2025-11-20 14:06:42 +05:30
ArnabChatterjee20k 553fe15ba9 resolved merge conflicts 2025-11-19 20:01:02 +05:30
ArnabChatterjee20k fec758d237 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-11-19 20:00:17 +05:30
ArnabChatterjee20k 21aae468f2 updated database type function 2025-11-14 19:35:53 +05:30
ArnabChatterjee20k f007b83ccc updated composer 2025-11-14 19:18:03 +05:30
ArnabChatterjee20k 28be12d827 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-11-14 19:16:08 +05:30
ArnabChatterjee20k 81128e8446 reverted changes 2025-11-06 17:51:33 +05:30
ArnabChatterjee20k 2bb6b0a2a9 updated database dsn for create in the create database 2025-10-31 16:45:01 +05:30
ArnabChatterjee20k 5d9ef0dcca added metadata create on database 2025-10-31 16:33:54 +05:30
ArnabChatterjee20k c8f966b0e5 added the default database type for the transactions context 2025-10-30 13:44:26 +05:30
ArnabChatterjee20k 3d2f21be7c updated composer lock 2025-10-30 13:02:49 +05:30
ArnabChatterjee20k 3e876f5bd8 fixed merge conflicts 2025-10-30 12:57:41 +05:30
ArnabChatterjee20k a5e56438c7 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-30 12:35:57 +05:30
ArnabChatterjee20k 4e79259c4e Refactor database handling to streamline DSN retrieval for documents and tables databases 2025-10-30 12:27:52 +05:30
ArnabChatterjee20k fae79e5c3f updated tests from feat-mongodb branch 2025-10-29 13:49:18 +05:30
ArnabChatterjee20k b169be9dda updated migration merge conflicts 2025-10-29 11:41:46 +05:30
ArnabChatterjee20k 8f4496346d Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-28 18:41:03 +05:30
ArnabChatterjee20k f306c03030 updated tests and index creation 2025-10-28 18:32:17 +05:30
ArnabChatterjee20k a8469a9b92 updated composer lock 2025-10-27 18:10:41 +05:30
ArnabChatterjee20k b63510acb5 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-27 18:10:08 +05:30
ArnabChatterjee20k 3e612ec88d flask test fixing via retry 2025-10-27 18:08:44 +05:30
ArnabChatterjee20k 1be36ae552 updated deps 2025-10-27 16:19:04 +05:30
ArnabChatterjee20k 445b657207 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-27 16:15:10 +05:30
ArnabChatterjee20k 5098973e0d reverted longtext resource file 2025-10-17 18:14:16 +05:30
ArnabChatterjee20k d0976ff429 updated registers config 2025-10-17 14:16:45 +05:30
ArnabChatterjee20k 157783f782 merged feat-mongodb 2025-10-17 13:58:27 +05:30
ArnabChatterjee20k e3a70373e1 updated docuemntsdb transaction path names 2025-10-16 22:19:53 +05:30
ArnabChatterjee20k a6e5563f8e reverted docker compose 2025-10-16 21:51:56 +05:30
ArnabChatterjee20k 470d84a843 * updated transactions
* updated tests
2025-10-16 21:45:30 +05:30
ArnabChatterjee20k 80037f7e8d updated http php for mongodb setup 2025-10-16 15:57:13 +05:30
ArnabChatterjee20k cc848c90a7 updated lock file 2025-10-16 14:25:22 +05:30
ArnabChatterjee20k e0c293c5b4 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-16 14:25:14 +05:30
ArnabChatterjee20k 2d82013261 updated docker compose setup for mongodb which was causing issue for mongo transactions 2025-10-16 14:22:28 +05:30
ArnabChatterjee20k f28588ecb6 updated migration 2025-10-15 14:24:59 +05:30
ArnabChatterjee20k 6579ea5b56 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-15 14:16:49 +05:30
ArnabChatterjee20k 9373688be2 updated migration worker to pass the dsn resolver for the destination database 2025-10-15 10:33:46 +05:30
ArnabChatterjee20k 2cec06be18 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-14 21:18:38 +05:30
ArnabChatterjee20k 3a60bb306c * updated migration tests
* database dsn fetching
* removed dsn from response models
2025-10-14 21:13:26 +05:30
ArnabChatterjee20k e2ddb0d6d0 updated migration 2025-10-14 13:23:48 +05:30
Darshan 0049265c8a Merge remote-tracking branch 'origin/documents-db-api' into documents-db-api 2025-10-14 12:49:41 +05:30
Darshan 1f27867565 update: method name. 2025-10-14 12:49:34 +05:30
ArnabChatterjee20k faf0cdec35 changed default fallbackForDB to mariadb 2025-10-14 12:46:01 +05:30
ArnabChatterjee20k 6073eab496 * updated env to have mongodb support for the tablesdb/legacy
* setAttributeSupport to support context schema and schemaless in mongodb
* updated the connection registers
2025-10-13 15:47:40 +05:30
ArnabChatterjee20k 50ddc611a4 updated migrations 2025-10-12 20:49:02 +05:30
ArnabChatterjee20k 0ef2cfa753 reverted docker compose 2025-10-12 20:26:39 +05:30
ArnabChatterjee20k 790e503ad9 updated composer lock 2025-10-12 20:19:57 +05:30
ArnabChatterjee20k f6dbc0c882 Merge remote-tracking branch 'upstream/feat-mongodb' into documents-db-api 2025-10-12 20:17:53 +05:30
ArnabChatterjee20k a6c226aac9 removed the documentsdb changes from the migration tests 2025-10-10 20:38:54 +05:30
ArnabChatterjee20k 2d41a3353d * migration fix for tablesdb, legacy with multitype callback
* updated patch script
2025-10-10 20:29:05 +05:30
ArnabChatterjee20k 34e4208b3e addressed comments
* renamed getDatabaseDB to getDatabasesDB
* renamed dbForDatabase to dbForDatabases
* removed call_user_func and using newer callable syntax
2025-10-10 20:00:43 +05:30
ArnabChatterjee20k d51486c62a * fixed stats usage events(hacky fix)
* updated redundant routes(happened during merge conflicts)
2025-10-10 19:39:59 +05:30
ArnabChatterjee20k f62f00d4d9 Merge remote-tracking branch 'upstream/documents-db-api' into documents-db-api 2025-10-10 15:26:33 +05:30
ArnabChatterjee20k 44e6ef3146 event generation fix 2025-10-10 15:24:42 +05:30
Jake BarnbyandGitHub dea49d54c9 Merge pull request #10628 from appwrite/fix-enums
Update: enums
2025-10-10 22:16:45 +13:00
Darshan 8a9523fa48 update: enums. 2025-10-10 14:40:26 +05:30
Darshan 5dba0183c0 update: enums. 2025-10-10 14:40:01 +05:30
ArnabChatterjee20k 0108f86180 linting + merge conflicts resolving 2025-10-10 13:39:16 +05:30
ArnabChatterjee20k bc71d83697 empty commit 2025-10-10 10:58:11 +05:30
ArnabChatterjee20k 0754c6263e merged changes from 1.8.x 2025-10-10 10:55:48 +05:30
ArnabChatterjee20k 2c037803f0 updated sdk names and enums for documentsdb 2025-10-09 18:41:10 +05:30
ArnabChatterjee20k 48410d0cf6 * added getDatabaseDB in stats worker, migration worker
* added prefixed events for multitype db support with backward compat
* added realtime tests, migration tests
2025-10-09 18:26:05 +05:30
ArnabChatterjee20k 66b0f3c9df * added waiting time in usage test so that aggregator can work
* added documentsdb usage test
2025-10-09 18:24:04 +05:30
ArnabChatterjee20k d8910eea73 fix - updated documentsdb sdk namespace to documentsDB 2025-10-09 10:30:57 +05:30
Jake Barnby 38b04566b6 Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb
# Conflicts:
#	src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php
#	src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php
#	src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php
2025-10-09 17:36:22 +13:00
Jake Barnby c89af1201a Fix tests 2025-10-09 16:37:17 +13:00
Jake Barnby 9f85ca83b4 Fix roles param 2025-10-09 14:33:05 +13:00
Jake Barnby d5d09061e9 Fix index validator params 2025-10-09 13:46:14 +13:00
ArnabChatterjee20k f7f965a812 * added prefixed channels for realtime channels along with backward compatibility
* added documentsdb tests
2025-10-09 00:35:38 +05:30
Jake Barnby 143be8239f Fix inject 2025-10-09 00:41:25 +13:00
ArnabChatterjee20k d806bdbca7 added project region in the get database dsn 2025-10-08 11:17:12 +05:30
ArnabChatterjee20k 19b1196633 * new db pool implementation for connecting to db type
* fixed migration tests
2025-10-08 11:09:32 +05:30
Jake Barnby da519046dd Merge branch 'feat-mongodb' of github.com:appwrite/appwrite into feat-mongodb 2025-10-08 00:05:05 +13:00
Jake Barnby 0c5573ab05 Fix list wrapping UID injections 2025-10-08 00:04:52 +13:00
ArnabChatterjee20k 88f2b1ad50 added patch script for adding database dsn in the databases collection 2025-10-07 13:18:11 +05:30
ArnabChatterjee20k f5ea19b15d updated tests 2025-10-07 12:45:21 +05:30
Jake BarnbyandGitHub fa60f03536 Merge branch '1.8.x' into feat-mongodb 2025-10-07 18:52:28 +13:00
Jake Barnby 28392647e0 Pass adapter max ID length to validator 2025-10-07 18:52:05 +13:00
ArnabChatterjee20k d2645ecfb1 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-07 10:47:26 +05:30
Jake Barnby 4b0eddcc84 Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb
# Conflicts:
#	composer.json
#	composer.lock
2025-10-07 18:16:02 +13:00
Jake Barnby 94e40c0127 Update database 2025-10-04 10:15:13 +13:00
ArnabChatterjee20k 44b75d50f4 revmoved databasse logs xlist action for documentsdb 2025-10-03 19:56:20 +05:30
ArnabChatterjee20k df23aacbc0 updated routes and endpoints 2025-10-03 19:51:00 +05:30
ArnabChatterjee20k 7d4dfe06e2 * removed dbForDocumentsRecords
* using callback based db resolution
* updated env
2025-10-03 19:50:29 +05:30
Jake Barnby 498235a2ef Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb 2025-10-03 23:19:33 +13:00
Jake Barnby 52c9671157 Update deps 2025-10-03 23:19:09 +13:00
ArnabChatterjee20k e50af4c981 add references docs for the documentsdb 2025-10-02 19:24:35 +05:30
ArnabChatterjee20k 4fdab6fd52 updated database db resolution in the resources itself instead of switch in the worker action 2025-10-02 18:58:11 +05:30
ArnabChatterjee20k c54450f582 updated composer 2025-10-02 18:57:15 +05:30
ArnabChatterjee20k 6bf9693146 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-10-02 18:56:48 +05:30
Jake Barnby 61a1efd0fe Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb 2025-10-02 19:25:56 +13:00
Jake Barnby faf837e389 Update db for index validation fix 2025-10-02 15:29:27 +13:00
Jake Barnby 3ecd7e6a95 Fix fts create on array 2025-10-01 22:02:41 +13:00
Jake Barnby 36782650c6 Revert "Add CI debug"
This reverts commit 2d3dea84e3.
2025-10-01 21:36:03 +13:00
Jake Barnby 2f6bc9115f Fix workflows init 2025-10-01 21:35:55 +13:00
Jake Barnby 2d3dea84e3 Add CI debug 2025-10-01 21:10:26 +13:00
Jake Barnby b12f4008eb Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb
# Conflicts:
#	composer.lock
2025-10-01 20:13:48 +13:00
Jake Barnby 408bca400b Fix spatial tests 2025-10-01 19:33:11 +13:00
Jake Barnby 6bacec52c5 Fix storage test 2025-10-01 17:11:02 +13:00
Jake Barnby 9d70b5cab2 Fix domain test 2025-10-01 17:09:07 +13:00
Jake Barnby d9baf5b628 Fix sites error 2025-10-01 12:31:07 +13:00
Jake Barnby 9e837c2eb9 Fix storage error 2025-10-01 12:31:02 +13:00
Jake Barnby f3460db369 Fix fulltext error 2025-10-01 12:30:48 +13:00
ArnabChatterjee20k 422fd8b157 updated get endpoint 2025-09-30 20:02:30 +05:30
ArnabChatterjee20k bf061b460b added documentsdb in the github actions 2025-09-30 19:29:36 +05:30
ArnabChatterjee20k a035760b00 updated injections and reverted reduntant changes 2025-09-30 19:15:43 +05:30
ArnabChatterjee20k 85c0454c49 linting 2025-09-30 18:45:53 +05:30
Jake Barnby b57839402f Update DB 2025-10-01 02:14:51 +13:00
ArnabChatterjee20k 7bfb0401c6 linting 2025-09-30 18:44:27 +05:30
ArnabChatterjee20k c097f4a450 updated composer lock 2025-09-30 18:43:02 +05:30
ArnabChatterjee20k a3faa108c5 Merge remote-tracking branch 'upstream/1.8.x' into documents-db-api 2025-09-30 18:42:18 +05:30
ArnabChatterjee20k c89b76ba80 *updated tests
*updated create documents to use the on next callback
2025-09-30 18:19:44 +05:30
Jake Barnby 763158fe81 Fix usage tests 2025-10-01 01:30:18 +13:00
Jake Barnby 766c3293d2 Fix index text checks 2025-10-01 00:47:07 +13:00
Jake Barnby 1226c0834c Fix upsert 2025-10-01 00:13:09 +13:00
Jake Barnby f0983cd9ff Remove mounts 2025-10-01 00:03:07 +13:00
Jake Barnby daeb721995 Fix tests 2025-09-30 23:44:26 +13:00
Jake Barnby 0555a27656 Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb 2025-09-30 22:58:38 +13:00
Jake Barnby da3284ce84 Update deps 2025-09-30 22:58:29 +13:00
ArnabChatterjee20k 80636e8a91 updated timeouts 2025-09-30 00:03:12 +05:30
Jake Barnby 53f8a3a78b Update DB 2025-09-29 19:39:51 +13:00
ArnabChatterjee20k 206845e106 updated tests and delete delete documents 2025-09-28 14:43:44 +05:30
ArnabChatterjee20k 64d015065f * added new databasetype dsn
* updated worker to use the dsn to choose the database resource
2025-09-26 19:52:25 +05:30
ArnabChatterjee20k 56af56e341 updated endpoints 2025-09-26 18:01:39 +05:30
Jake Barnby aa77253b14 Fix OSV 2025-09-26 22:17:01 +12:00
Jake Barnby 78c5743e75 Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb
# Conflicts:
#	composer.lock
2025-09-26 22:14:40 +12:00
Jake Barnby 156a944b54 Merge remote-tracking branch 'origin/feat-mongodb-index-validation' into feat-mongodb 2025-09-26 22:05:31 +12:00
Jake Barnby b07fd5afe6 Merge remote-tracking branch 'origin/feat-mongodb-pulls' into feat-mongodb 2025-09-26 22:05:24 +12:00
ArnabChatterjee20k 3086e1cbc1 updated increment, and decrements routes 2025-09-25 19:58:59 +05:30
ArnabChatterjee20k b032a55045 updated the endpoints 2025-09-25 00:48:09 +05:30
ArnabChatterjee20k d71c56b11f * Migrating endpoints
* New adapter resolution and injection
* Removed attribute creation for documentsdb
2025-09-23 23:15:46 +05:30
shimon b828621feb Update composer dependencies and enhance index validation in database module 2025-09-21 19:14:40 +03:00
shimon 3edc055c9d Skip spatial column tests for MongoDB support in DatabasesBase trait 2025-09-21 16:02:36 +03:00
shimon ceda513083 Merge branch '1.8.x' of github.com:appwrite/appwrite into feat-mongodb-pulls
# Conflicts:
#	composer.lock
#	src/Appwrite/Platform/Workers/StatsUsage.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
2025-09-21 15:55:11 +03:00
Shimon NewmanandGitHub 2d47b88246 Merge pull request #10527 from appwrite/feat-mongodb-pulls
Feat mongodb pulls
2025-09-21 15:46:15 +03:00
shimon b88e74095b Merge remote-tracking branch 'origin/feat-mongodb-pulls' into feat-mongodb-pulls
# Conflicts:
#	composer.json
2025-09-21 15:44:41 +03:00
shimon 4d85c5ca4f composer 2025-09-21 15:42:44 +03:00
shimon c89e649d05 composer 2025-09-21 15:38:35 +03:00
shimon d9bdf01cee db lib ver 2025-09-21 15:30:09 +03:00
shimon 54e3a4b25d db lib ver 2025-09-21 15:27:41 +03:00
ArnabChatterjee20k 98ee242812 migrated tests 2025-09-20 18:21:00 +05:30
ArnabChatterjee20k 79267c8123 migrated endpoints 2025-09-20 18:20:50 +05:30
ArnabChatterjee20k d2ad0e7c74 documentsdb database creation endpoints 2025-09-19 19:44:46 +05:30
shimon b1e55702c8 Refactor MongoDB test cases for improved readability and consistency. Adjust whitespace in conditional statements and remove redundant test methods related to spatial columns. Add new tests for spatial distance queries and update existing tests to ensure proper functionality. 2025-09-16 19:14:09 +03:00
shimon f16eba2f9f Update PHPUnit configuration to stop on failure set to false, and clean up whitespace in multiple files for consistency. Adjust comments and ensure proper formatting in test cases and API handling. 2025-09-16 19:13:38 +03:00
shimon 7c0bd1da88 uncomment inversion tests 2025-09-16 17:51:29 +03:00
shimon cc87de8934 Merge branch 'feat-mongodb-pulls' of github.com:appwrite/appwrite into feat-mongodb-pulls
# Conflicts:
#	composer.lock
2025-09-16 17:43:47 +03:00
shimon 5f8dc9998c Refactor database document handling by replacing createOrUpdateDocuments with upsertDocuments in multiple classes. Clean up common.php by removing commented-out index definitions and ensuring proper indexing for MongoDB compatibility. Update UsageTest to correct API endpoint paths for database operations. 2025-09-16 17:39:41 +03:00
shimon cc7778ceea database lib check 2025-09-16 11:10:06 +03:00
shimon f5fc099d87 Merge branch '1.8.x' of github.com:appwrite/appwrite into feat-mongodb-pulls
# Conflicts:
#	composer.lock
#	tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
2025-09-16 10:26:43 +03:00
shimon 89d06d00c0 Merge branch '1.8.x' of github.com:appwrite/appwrite into feat-mongodb-pulls
# Conflicts:
#	.github/workflows/tests.yml
#	composer.lock
#	tests/e2e/Services/Databases/TablesDB/DatabasesBase.php
2025-09-11 16:54:49 +03:00
shimon 882cd8c83e Merge remote-tracking branch 'origin/feat-mongodb-pulls' into feat-mongodb-pulls
# Conflicts:
#	tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php
2025-09-11 16:43:39 +03:00
shimon 80cc6a0bb3 DatabasesCustomClientTest.php 2025-09-11 16:42:33 +03:00
shimon fc4ad175dd Update composer.json and composer.lock to reflect new versioning for utopia-php/database and other dependencies. Adjusted package versions for brick/math, open-telemetry/api, and symfony components. Added symfony/polyfill-php83 for compatibility with PHP 8.3 features. Minor formatting changes in MigrationsBase.php for code clarity. 2025-09-11 16:38:41 +03:00
shimon a0def8edf8 Updates 2025-08-31 17:57:58 +03:00
shimon bdb8204910 Refactor database attribute tests to streamline size validation and add support for additional integer index. Updated size constraints for 'description' and 'tagline' attributes, and introduced a new 'integers2' attribute to accommodate MongoDB limitations. Adjusted assertions to reflect these changes. 2025-08-27 16:32:13 +03:00
shimon 1274614d18 Update composer.json and composer.lock to reflect new versioning for utopia-php/database and other dependencies. Adjusted Docker configuration to set the database adapter to MariaDB. 2025-08-27 13:55:48 +03:00
shimon 4f937ac549 Update composer.json and composer.lock to reflect changes in utopia-php/database versioning, including the removal of the 'with-uuid7' suffix. Updated dependencies for composer/semver, symfony/polyfill packages, and phpunit/phpunit to their latest versions for improved compatibility and performance. 2025-08-27 10:28:17 +03:00
shimon 6910d669de Update composer.json to correct utopia-php/database version reference and modify test to use the correct endpoint for MongoDB operations. 2025-08-27 10:25:23 +03:00
shimon 4fb680350a Merge branch '1.8.x' of github.com:appwrite/appwrite into feat-mongodb-pulls
# Conflicts:
#	Dockerfile
#	composer.json
#	composer.lock
#	tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php
#	tests/e2e/Services/Databases/TablesDB/DatabasesCustomServerTest.php
2025-08-26 14:20:47 +03:00
Shimon NewmanandGitHub ff35dc6114 Merge pull request #10375 from appwrite/feat-mongodb-pulls
Feat mongodb pulls
2025-08-26 13:53:42 +03:00
shimon 38433b9ac1 Remove unnecessary var_dump statement from ProjectsConsoleClientTest for improved code clarity and consistency. 2025-08-26 13:52:05 +03:00
shimon b98be4756c Update composer.json and composer.lock to reflect changes in utopia-php/database versioning and dependencies. Adjusted MongoDB-related test conditions for improved compatibility and clarity. Removed unnecessary var_dump statements in account.php and enhanced logging in various test files for better debugging. 2025-08-26 13:48:01 +03:00
shimon ff8418ffb9 Update composer.lock to reflect new references and timestamps for dependencies, including updates for utopia-php/database and phpunit/phpunit. Removed duplicate entries and unnecessary lines for improved clarity. 2025-08-12 18:01:38 +03:00
shimon e151425b95 Merge branch '1.8.x' of github.com:appwrite/appwrite into feat-mongodb-pulls 2025-08-12 17:54:55 +03:00
shimon b9f4815c3a Remove unnecessary var_dump statements from api.php and enhance logging in MessagingConsoleClientTest for improved clarity and debugging. This aligns with recent efforts to clean up debugging output across the codebase. 2025-08-12 17:47:11 +03:00
shimon c4fb7ecbc1 Refactor and clean up debugging output across multiple files, including mock.php, account.php, and OAuth2 classes. Removed unnecessary var_dump statements for improved code clarity and consistency. Updated MongoDB condition checks in test cases to ensure case-insensitive comparisons. 2025-08-12 15:20:30 +03:00
shimon 24b7c95514 Merge branch 'feat-mongodb' of github.com:appwrite/appwrite into feat-mongodb 2025-08-11 22:07:11 +03:00
shimon 870125ed84 Refactor MongoDB condition checks in DatabasesCustomClientTest and UsersBase. Improved formatting for consistency and clarity in test assertions, particularly for MongoDB-related tests. 2025-08-11 22:07:05 +03:00
shimon e1475619ba Refactor debugging output across various files, including http.php, mock.php, and OAuth2 classes. Adjusted var_dump statements for improved traceability and consistency. Updated test assertions in ProjectsConsoleClientTest and UsersBase to enhance clarity and functionality. 2025-08-11 22:05:18 +03:00
Matej Bačo ba058a89a3 Merge branch '1.8.x' into feat-mongodb 2025-08-11 13:22:27 +02:00
shimon 236fab4658 Update composer configuration for minimum stability and prefer stable packages. Adjust utopia-php/database version alias and update phpunit and sebastian packages to latest versions. Add debugging output in http.php and uncomment OAuth2 conversion test in AccountCustomClientTest for improved functionality. 2025-08-11 11:56:38 +03:00
shimon 6cfd6d9ac9 Remove debugging var_dump statements from projects API and enhance error handling in XList class. Update test cases to comment out a specific OAuth2 conversion test for clarity. 2025-08-11 11:28:11 +03:00
shimon da706378ea Enhance debugging output in OAuth2 mock endpoints and update MongoDB version in tests. Added var_dump statements for better traceability during OAuth2 flows and adjusted test assertions for user search functionality. 2025-08-11 11:27:45 +03:00
shimon 40e36e23dd Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb 2025-08-10 18:16:03 +03:00
shimon 603980759a composer.lock 2025-08-06 15:49:05 +03:00
shimon d63a218bd8 Merge branch '1.8.x' of github.com:appwrite/appwrite into feat-mongodb
# Conflicts:
#	composer.json
#	composer.lock
2025-08-06 15:48:19 +03:00
shimon def3628205 feat: rename tables to grids and update documentation 2025-08-06 15:37:59 +03:00
shimon 779fb7bd20 Update composer dependencies and Docker Compose configuration for MongoDB integration. Change database alias in composer.json, update package versions in composer.lock, and adjust volume mappings in Docker Compose to include MongoDB source directories. Refactor application code to set tenant to null for database connections. 2025-08-06 15:07:04 +03:00
shimon ea1aef1d70 composer.lock 2025-08-03 18:40:45 +03:00
shimon b1cc203eba composer 2025-08-03 18:38:38 +03:00
shimon b3e1a3415a Merge branch '1.8.x' of github.com:appwrite/appwrite into feat-mongodb
# Conflicts:
#	app/init/resources.php
#	composer.json
#	composer.lock
2025-08-03 18:33:27 +03:00
shimon d48248c9a2 Refactor Docker Compose and application code for MongoDB integration. Remove health check configuration and simplify service dependencies in Docker Compose. Update error logging in application to include context for MongoDB connection issues. 2025-08-03 18:13:16 +03:00
shimon eafca0c431 Add support for multiple database adapters in GitHub Actions workflow, including MARIADB and MONGODB. Set environment variables based on selected adapter for testing. 2025-07-31 18:22:42 +03:00
shimon a92bdfaed0 Update Docker Compose to clarify MongoDB readiness check comment, addressing implications of template substitution limitations. 2025-07-31 17:49:04 +03:00
shimon 46965135a4 Update Docker Compose and application configuration to support MongoDB as the primary database, replacing all instances of MariaDB. Introduce a new environment variable for database selection and adjust database schemes in the application initialization. 2025-07-31 17:17:08 +03:00
shimon 33c5ac83b2 Enhance Docker Compose configuration for MongoDB, add health checks, and update user creation logic in mongo-init.js. Improve database connection handling and fix conditional checks in application code. 2025-07-31 12:47:12 +03:00
shimon 57c08a41f1 Update environment configuration to support MongoDB, enhance Docker Compose for MongoDB integration, and improve database connection handling in the application. 2025-07-29 23:08:00 +03:00
shimon 36e7452f78 Update environment variables, Dockerfile version, and enhance MongoDB connection handling in the application. Add console document creation logic and improve code formatting for better readability. 2025-07-28 17:34:11 +03:00
shimon 469801b862 Update database configuration to use MongoDB, add MongoDB and Mongo Express services to Docker Compose, and adjust related code for MongoDB integration. 2025-07-27 20:38:49 +03:00
7425 changed files with 52832 additions and 75450 deletions
-31
View File
@@ -1,31 +0,0 @@
# PR Workflow for Appwrite
## Branch Targeting
**Important:** Appwrite does NOT use `main` as the primary development branch.
PRs should target the **current version branch** (e.g., `1.8.x`, `1.9.x`), not `main`.
### How to determine the correct target branch
1. Check which version branch you're currently on: `git branch --show-current`
2. Look for branches matching the pattern `X.Y.x` (e.g., `1.8.x`, `1.9.x`)
3. The current active development branch is typically the highest version number with the `.x` suffix
### When creating PRs
Always use the version branch as the base:
```bash
# Correct - targets the version branch
gh pr create --base 1.8.x --title "Your PR title" --body "..."
# Wrong - do not target main
gh pr create --base main ...
```
### Branch naming convention
- `X.Y.x` branches (e.g., `1.8.x`) - Active development branches for each minor version
- `main` - Not used for regular PRs; reserved for release management
- Feature branches should be created from and merged back into the current version branch
+7 -9
View File
@@ -2,7 +2,6 @@ _APP_ENV=development
_APP_EDITION=self-hosted
_APP_LOCALE=en
_APP_WORKER_PER_CORE=6
_APP_COMPRESSION_ENABLED=enabled
_APP_COMPRESSION_MIN_SIZE_BYTES=1024
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
@@ -10,7 +9,6 @@ _APP_CONSOLE_SESSION_ALERTS=enabled
_APP_CONSOLE_WHITELIST_IPS=
_APP_CONSOLE_COUNTRIES_DENYLIST=AQ
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_MIGRATION_HOST=appwrite
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io
_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
@@ -26,9 +24,8 @@ _APP_OPENSSL_KEY_V1=your-secret-key
_APP_DNS=172.16.238.100 # CoreDNS
_APP_DOMAIN=appwrite.test
_APP_CONSOLE_DOMAIN=localhost
_APP_CONSOLE_TRUSTED_PROJECTS=trusted-project,another-trusted-project
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost,rebranded.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=cname.localhost
_APP_DOMAIN_TARGET_A=203.0.0.1
_APP_DOMAIN_TARGET_AAAA=::1
@@ -40,10 +37,14 @@ _APP_REDIS_PASS=
_APP_REDIS_USER=
_APP_DB_HOST=mariadb
_APP_DB_PORT=3306
_APP_DB_ADAPTER=mariadb
_APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_DB_ROOT_PASS=rootsecretpassword
_APP_DB_ADAPTER_DOCUMENTSDB=mongodb
_APP_DB_HOST_DOCUMENTSDB=mongodb
_APP_DB_PORT_DOCUMENTSDB=27017
_APP_STORAGE_DEVICE=Local
_APP_STORAGE_S3_ACCESS_KEY=
_APP_STORAGE_S3_SECRET=
@@ -104,12 +105,10 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
_APP_STATS_RESOURCES_INTERVAL=30
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_INTERVAL_DOMAIN_VERIFICATION=120
_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300
_APP_INTERVAL_DOMAIN_VERIFICATION=60
_APP_USAGE_STATS=enabled
_APP_LOGGING_CONFIG=
_APP_LOGGING_CONFIG_REALTIME=
_APP_GRAPHQL_INTROSPECTION=enabled
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=4
@@ -131,5 +130,4 @@ _APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_PROJECT_REGIONS=default
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for
_APP_POOL_ADAPTER=stack
_APP_TRUSTED_HEADERS=x-forwarded-for
+30 -15
View File
@@ -151,10 +151,8 @@ jobs:
- name: Failure Logs
if: failure()
run: |
echo "=== Appwrite Worker Builds Logs ==="
docker compose logs appwrite-worker-builds
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
echo "=== Appwrite Logs ==="
docker compose logs
e2e_service_test:
name: E2E Service Test
@@ -163,12 +161,17 @@ jobs:
strategy:
fail-fast: false
matrix:
db_adapter: [
MARIADB,
MONGODB
]
service: [
Account,
Avatars,
Console,
Databases/Legacy,
Databases/TablesDB,
Databases/DocumentsDB,
Functions,
FunctionsSchedule,
GraphQL,
@@ -202,6 +205,18 @@ jobs:
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
sed -i 's|^_APP_BROWSER_HOST=.*|_APP_BROWSER_HOST=http://invalid-browser/v1|' .env
# Set DB Adapter in .env before starting services
if [ "${{ matrix.db_adapter }}" = "MARIADB" ]; then
sed -i 's|^_APP_DB_ADAPTER=.*|_APP_DB_ADAPTER=mariadb|' .env
sed -i 's|^_APP_DB_HOST=.*|_APP_DB_HOST=mariadb|' .env
sed -i 's|^_APP_DB_PORT=.*|_APP_DB_PORT=3306|' .env
elif [ "${{ matrix.db_adapter }}" = "MONGODB" ]; then
sed -i 's|^_APP_DB_ADAPTER=.*|_APP_DB_ADAPTER=mongodb|' .env
sed -i 's|^_APP_DB_HOST=.*|_APP_DB_HOST=mongodb|' .env
sed -i 's|^_APP_DB_PORT=.*|_APP_DB_PORT=27017|' .env
fi
docker compose up -d
sleep 30
@@ -223,7 +238,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group abuseEnabled,screenshots
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
- name: Failure Logs
if: failure()
@@ -312,7 +327,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group abuseEnabled,screenshots
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug --exclude-group devKeys,screenshots
- name: Failure Logs
if: failure()
@@ -322,8 +337,8 @@ jobs:
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
e2e_abuse_enabled:
name: E2E Service Test (Abuse enabled)
e2e_dev_keys:
name: E2E Service Test (Dev Keys)
runs-on: ubuntu-latest
needs: setup
steps:
@@ -344,7 +359,7 @@ jobs:
docker compose up -d
sleep 30
- name: Run Projects tests in dedicated table mode
- name: Run Projects tests with dev keys in dedicated table mode
run: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
@@ -354,7 +369,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
- name: Failure Logs
if: failure()
@@ -364,8 +379,8 @@ jobs:
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
e2e_abuse_enabled_shared_mode:
name: E2E Shared Mode Service Test (Abuse enabled)
e2e_dev_keys_shared_mode:
name: E2E Shared Mode Service Test (Dev Keys)
runs-on: ubuntu-latest
needs: [ setup, check_database_changes ]
if: needs.check_database_changes.outputs.database_changed == 'true'
@@ -394,7 +409,7 @@ jobs:
docker compose up -d
sleep 30
- name: Run Projects tests in ${{ matrix.tables-mode }} table mode
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
run: |
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
@@ -410,7 +425,7 @@ jobs:
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
-e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=abuseEnabled
appwrite test /usr/src/code/tests/e2e/Services/Projects --debug --group=devKeys
- name: Failure Logs
if: failure()
@@ -420,7 +435,7 @@ jobs:
echo "=== OpenRuntimes Executor Logs ==="
docker compose logs openruntimes-executor
e2e_screenshots:
e2e_screenshots_keys:
name: E2E Service Test (Site Screenshots)
runs-on: ubuntu-latest
needs: setup
-4
View File
@@ -58,10 +58,6 @@ Examples:
Avoid introducing new dependencies other than utopia-php.
## Adding new endpoints
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
## Pull Request Guidelines
### Before Submitting
-98
View File
@@ -1,101 +1,3 @@
# Version 1.8.1
## What's Changed
### Notable changes
* Add branch deployments support in [#10486](https://github.com/appwrite/appwrite/pull/10486)
* Add TanStack Start sites support in [#10681](https://github.com/appwrite/appwrite/pull/10681)
* Add Next.js standalone support in [#10747](https://github.com/appwrite/appwrite/pull/10747)
* Add Resend integration in [#10690](https://github.com/appwrite/appwrite/pull/10690)
* Add option to enable/disable image transformations per-bucket in [#10722](https://github.com/appwrite/appwrite/pull/10722)
* Add operators support in [#10735](https://github.com/appwrite/appwrite/pull/10735) and [#10800](https://github.com/appwrite/appwrite/pull/10800)
* Add function and sites stats in [#10786](https://github.com/appwrite/appwrite/pull/10786)
* Add disable count feature in [#10668](https://github.com/appwrite/appwrite/pull/10668)
* Add ElevenLabs site template in [#10782](https://github.com/appwrite/appwrite/pull/10782)
* Add suggested environment variables in [#10795](https://github.com/appwrite/appwrite/pull/10795)
* Update GeoDB database in [#10890](https://github.com/appwrite/appwrite/pull/10890)
* Update Flutter default build runtime in [#10807](https://github.com/appwrite/appwrite/pull/10807)
* Upgrade runtimes in [#10804](https://github.com/appwrite/appwrite/pull/10804)
### Fixes
* Fix duplicate document error while creating file in [#10891](https://github.com/appwrite/appwrite/pull/10891)
* Fix "Update external deployment (authorize)" throwing 500 error due to invalid query in [#10888](https://github.com/appwrite/appwrite/pull/10888)
* Fix error setting user password in [#10889](https://github.com/appwrite/appwrite/pull/10889)
* Fix error generating email MFA challenges in [#10884](https://github.com/appwrite/appwrite/pull/10884)
* Fix file token expiry in [#10877](https://github.com/appwrite/appwrite/pull/10877)
* Fix TanStack Nitro default in [#10860](https://github.com/appwrite/appwrite/pull/10860)
* Fix TanStack builds in [#10767](https://github.com/appwrite/appwrite/pull/10767)
* Fix nullable validation in [#10819](https://github.com/appwrite/appwrite/pull/10819) and [#10778](https://github.com/appwrite/appwrite/pull/10778)
* Fix WebP library in [#10738](https://github.com/appwrite/appwrite/pull/10738)
* Fix batch writes in [#10812](https://github.com/appwrite/appwrite/pull/10812)
* Fix error handler error in [#10719](https://github.com/appwrite/appwrite/pull/10719)
* Fix Next 16 compatibility in [#10713](https://github.com/appwrite/appwrite/pull/10713)
* Fix stats usage memory leak in [#10683](https://github.com/appwrite/appwrite/pull/10683)
* Fix author URL in template deployments in [#10535](https://github.com/appwrite/appwrite/pull/10535)
* Fix VCS lock deletion in [#10691](https://github.com/appwrite/appwrite/pull/10691)
### Miscellaneous
* Add CSV export functionality in [#10546](https://github.com/appwrite/appwrite/pull/10546), [#10750](https://github.com/appwrite/appwrite/pull/10750), [#10813](https://github.com/appwrite/appwrite/pull/10813), and [#10847](https://github.com/appwrite/appwrite/pull/10847)
* Add JWT disposition in [#10867](https://github.com/appwrite/appwrite/pull/10867)
* Add screenshots endpoint in [#10675](https://github.com/appwrite/appwrite/pull/10675)
* Add screenshot endpoint stats in [#10706](https://github.com/appwrite/appwrite/pull/10706)
* Add users attributes in [#10688](https://github.com/appwrite/appwrite/pull/10688)
* Add max build duration environment variable in [#10674](https://github.com/appwrite/appwrite/pull/10674)
* Add custom realtime logger in [#10871](https://github.com/appwrite/appwrite/pull/10871)
* Add logs in [#10869](https://github.com/appwrite/appwrite/pull/10869)
* Improve MFA docs endpoint order in [#10793](https://github.com/appwrite/appwrite/pull/10793)
* Auth refactor in [#10758](https://github.com/appwrite/appwrite/pull/10758), [#10837](https://github.com/appwrite/appwrite/pull/10837), [#10682](https://github.com/appwrite/appwrite/pull/10682), and [#10667](https://github.com/appwrite/appwrite/pull/10667)
* Bump assistant to 0.8.4 in [#10887](https://github.com/appwrite/appwrite/pull/10887)
* Bump database to 3.1.5 in [#10766](https://github.com/appwrite/appwrite/pull/10766)
* Bump Utopia DNS in [#10761](https://github.com/appwrite/appwrite/pull/10761)
* Update domains to 0.8.3 in [#10658](https://github.com/appwrite/appwrite/pull/10658)
* Update domains to 0.9.1 in [#10678](https://github.com/appwrite/appwrite/pull/10678)
* Update Apple Swift to 13.3.0 in [#10679](https://github.com/appwrite/appwrite/pull/10679)
* Update Apple Swift in [#10663](https://github.com/appwrite/appwrite/pull/10663)
* Update CLI to 10.2.2 in [#10672](https://github.com/appwrite/appwrite/pull/10672)
* Update to CLI 12.0.0 in [#10853](https://github.com/appwrite/appwrite/pull/10853)
* Update docs examples to use Permission class in [#10707](https://github.com/appwrite/appwrite/pull/10707)
* Update SDK examples docs in [#10855](https://github.com/appwrite/appwrite/pull/10855)
* Release Python SDK in [#10762](https://github.com/appwrite/appwrite/pull/10762)
* Release Flutter 20.3.2 in [#10838](https://github.com/appwrite/appwrite/pull/10838)
* Release Flutter/Dart add screenshot examples in [#10811](https://github.com/appwrite/appwrite/pull/10811)
* Release PHP CLI in [#10791](https://github.com/appwrite/appwrite/pull/10791)
* Release SDKs in [#10817](https://github.com/appwrite/appwrite/pull/10817)
* Update SDKs in [#10694](https://github.com/appwrite/appwrite/pull/10694), [#10729](https://github.com/appwrite/appwrite/pull/10729), and [#10744](https://github.com/appwrite/appwrite/pull/10744)
* Update SDK generator in [#10743](https://github.com/appwrite/appwrite/pull/10743)
* Update database in [#10664](https://github.com/appwrite/appwrite/pull/10664)
* Update README file in [#10763](https://github.com/appwrite/appwrite/pull/10763)
* SDK release documentation in [#10745](https://github.com/appwrite/appwrite/pull/10745)
* SDK release runtime config in [#10765](https://github.com/appwrite/appwrite/pull/10765)
* Sync specs in [#10789](https://github.com/appwrite/appwrite/pull/10789)
* Sync 1.8.0 in [#10677](https://github.com/appwrite/appwrite/pull/10677)
* Add workflow for issue triage in [#10718](https://github.com/appwrite/appwrite/pull/10718)
* Add issue auto-labeler in [#10700](https://github.com/appwrite/appwrite/pull/10700)
* Add AI moderator repo in [#10717](https://github.com/appwrite/appwrite/pull/10717)
* Browser bump in [#10850](https://github.com/appwrite/appwrite/pull/10850)
* Template type enum override in [#10848](https://github.com/appwrite/appwrite/pull/10848)
* VCS reference type in [#10852](https://github.com/appwrite/appwrite/pull/10852)
* Index scope description in [#10851](https://github.com/appwrite/appwrite/pull/10851)
* Config for environment in [#10833](https://github.com/appwrite/appwrite/pull/10833)
* Format instance in [#10830](https://github.com/appwrite/appwrite/pull/10830)
* Replace sleep in webhooks service in [#10656](https://github.com/appwrite/appwrite/pull/10656)
* Update email composer in [#10720](https://github.com/appwrite/appwrite/pull/10720)
* Update facts on GitHub sites and functions in [#10593](https://github.com/appwrite/appwrite/pull/10593) and [#10771](https://github.com/appwrite/appwrite/pull/10771)
* Fix wrong user type in [#10875](https://github.com/appwrite/appwrite/pull/10875)
* Fix limit and offset computation in [#10880](https://github.com/appwrite/appwrite/pull/10880)
* Fix enum examples in [#10828](https://github.com/appwrite/appwrite/pull/10828)
* Fix response models multi-methods in [#10815](https://github.com/appwrite/appwrite/pull/10815)
* Fix undefined variable in [#10654](https://github.com/appwrite/appwrite/pull/10654)
* Fix undefined sequence in [#10652](https://github.com/appwrite/appwrite/pull/10652)
* Fix description in [#10702](https://github.com/appwrite/appwrite/pull/10702)
* Fix warning in builds worker in [#10705](https://github.com/appwrite/appwrite/pull/10705)
* Fix sites create deployment docs in [#10566](https://github.com/appwrite/appwrite/pull/10566)
* Fix test dependencies projects in [#10655](https://github.com/appwrite/appwrite/pull/10655)
* Fix list sites test in [#10726](https://github.com/appwrite/appwrite/pull/10726)
# Version 1.8.0
## What's Changed
+4 -3
View File
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.11.5 AS base
FROM appwrite/base:0.10.6 AS base
LABEL maintainer="team@appwrite.io"
@@ -37,6 +37,9 @@ COPY ./app /usr/src/code/app
COPY ./public /usr/src/code/public
COPY ./bin /usr/local/bin
COPY ./src /usr/src/code/src
COPY ./dev /usr/src/code/dev
COPY ./mongo-init.js /usr/src/code/mongo-init.js
COPY ./mongo-entrypoint.sh /usr/src/code/mongo-entrypoint.sh
# Set Volumes
RUN mkdir -p /storage/uploads && \
@@ -77,11 +80,9 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/queue-count-success && \
chmod +x /usr/local/bin/worker-audits && \
chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-screenshots && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-databases && \
chmod +x /usr/local/bin/worker-deletes && \
chmod +x /usr/local/bin/worker-executions && \
chmod +x /usr/local/bin/worker-functions && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
+3 -3
View File
@@ -72,7 +72,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.8.1
appwrite/appwrite:1.8.0
```
### Windows
@@ -84,7 +84,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.8.1
appwrite/appwrite:1.8.0
```
#### PowerShell
@@ -94,7 +94,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.8.1
appwrite/appwrite:1.8.0
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+3 -3
View File
@@ -75,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.8.1
appwrite/appwrite:1.8.0
```
### Windows
@@ -87,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.8.1
appwrite/appwrite:1.8.0
```
#### PowerShell
@@ -97,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.8.1
appwrite/appwrite:1.8.0
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+10 -35
View File
@@ -30,9 +30,6 @@ use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\Span\Exporter;
use Utopia\Span\Span;
use Utopia\Span\Storage;
use Utopia\System\System;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
@@ -44,7 +41,8 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
// require controllers after overwriting runtimes
require_once __DIR__ . '/controllers/general.php';
global $register;
Authorization::disable();
CLI::setResource('register', fn () => $register);
CLI::setResource('cache', function ($pools) {
@@ -62,13 +60,7 @@ CLI::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
CLI::setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
CLI::setResource('dbForPlatform', function ($pools, $cache) {
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
@@ -82,8 +74,6 @@ CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
@@ -109,7 +99,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
}
return $dbForPlatform;
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
CLI::setResource('console', function () {
return new Document(Config::getParam('console'));
@@ -120,10 +110,10 @@ CLI::setResource(
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -156,7 +146,6 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -173,19 +162,17 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
}, ['pools', 'dbForPlatform', 'cache']);
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
return $database;
@@ -195,8 +182,6 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
@@ -209,7 +194,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
return $database;
};
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
@@ -272,14 +257,6 @@ CLI::setResource('logError', function (Registry $register) {
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
}
$log->addExtra('previousFile', $error->getPrevious()->getFile());
$log->addExtra('previousLine', $error->getPrevious()->getLine());
}
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
@@ -329,6 +306,4 @@ $cli
$cli->shutdown()->action(fn () => Timer::clearAll());
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
Span::setStorage(new Storage\Coroutine());
Span::addExporter(new Exporter\Stdout());
run($cli->run(...));
+2 -2
View File
@@ -26,8 +26,8 @@ unset($common['files']);
$collections = [
'buckets' => $buckets,
'databases' => $databases,
'projects' => array_merge_recursive($projects, $common),
'console' => array_merge_recursive($platform, $common),
'projects' => array_merge($projects, $common),
'console' => array_merge($platform, $common),
'logs' => $logs,
];
+1 -32
View File
@@ -1288,17 +1288,6 @@ return [
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('labels'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
@@ -1617,13 +1606,6 @@ return [
],
],
'indexes' => [
[
'$id' => ID::custom('_fulltext_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
@@ -1853,13 +1835,6 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_type'),
'type' => Database::INDEX_KEY,
@@ -2127,14 +2102,8 @@ return [
'filters' => ['topicSearch'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['name'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
+59 -30
View File
@@ -78,6 +78,17 @@ $platformCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('documentsDatabase'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('logo'),
'type' => Database::VAR_STRING,
@@ -330,18 +341,7 @@ $platformCollections = [
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('labels'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
]
],
'indexes' => [
[
@@ -633,7 +633,29 @@ $platformCollections = [
'name' => 'keys',
'attributes' => [
[
'$id' => ID::custom('resourceType'),
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceType',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -644,7 +666,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'$id' => 'resourceId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -655,7 +677,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'$id' => 'resourceInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -734,14 +756,21 @@ $platformCollections = [
],
'indexes' => [
[
'$id' => ID::custom('_key_resource'),
'$id' => ID::custom('_key_project'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType', 'resourceInternalId'],
'lengths' => [],
'orders' => [],
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_accessedAt'),
'$id' => '_key_resource',
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType', 'resourceInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_accessedAt',
'type' => Database::INDEX_KEY,
'attributes' => ['accessedAt'],
'lengths' => [],
@@ -1384,21 +1413,21 @@ $platformCollections = [
'$id' => '_key_type',
'type' => Database::INDEX_KEY,
'attributes' => ['type'],
'lengths' => [],
'lengths' => [32],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_trigger',
'type' => Database::INDEX_KEY,
'attributes' => ['trigger'],
'lengths' => [],
'lengths' => [32],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_deploymentResourceType',
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentResourceType'],
'lengths' => [],
'lengths' => [32],
'orders' => [Database::ORDER_ASC],
],
[
@@ -1440,20 +1469,20 @@ $platformCollections = [
'$id' => ID::custom('_key_owner'),
'type' => Database::INDEX_KEY,
'attributes' => ['owner'],
'lengths' => [],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_piid_diid_drt'),
'$id' => ID::custom('_key_region'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'deploymentInternalId', 'deploymentResourceType'],
'lengths' => [],
'orders' => [],
'attributes' => ['region'],
'lengths' => [16],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_region_status_createdAt',
'$id' => ID::custom('_key_piid_riid_rt'),
'type' => Database::INDEX_KEY,
'attributes' => ['region', 'status', '$createdAt'],
'attributes' => ['projectInternalId', 'deploymentInternalId', 'deploymentResourceType'],
'lengths' => [],
'orders' => [],
],
+9 -106
View File
@@ -61,6 +61,15 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('database'),
'type' => Database::VAR_STRING,
'size' => 128,
'required' => true,
'signed' => true,
'array' => false,
'filters' => [],
]
],
'indexes' => [
[
@@ -567,17 +576,6 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentRetention'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
@@ -776,17 +774,6 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('specification'),
@@ -798,28 +785,6 @@ return [
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('runtimeSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'$id' => ID::custom('scopes'),
'type' => Database::VAR_STRING,
@@ -1079,17 +1044,6 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'$id' => ID::custom('fallbackFile'),
'type' => Database::VAR_STRING,
@@ -1101,17 +1055,6 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentRetention'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
@@ -1266,28 +1209,6 @@ return [
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('runtimeSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'$id' => ID::custom('buildRuntime'),
'type' => Database::VAR_STRING,
@@ -1445,17 +1366,6 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildOutput'),
@@ -2197,13 +2107,6 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_resourceType'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
+1 -30
View File
@@ -357,11 +357,6 @@ return [
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::USER_JWT_AND_COOKIE_SET => [
'name' => Exception::USER_JWT_AND_COOKIE_SET,
'description' => 'JWT and cookie used in the same request. Use either `setJWT` or `setCookie`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::API_KEY_EXPIRED => [
'name' => Exception::API_KEY_EXPIRED,
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
@@ -1079,11 +1074,6 @@ return [
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ACCOUNT_KEY_EXPIRED => [
'name' => Exception::ACCOUNT_KEY_EXPIRED,
'description' => 'The account API key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ROUTER_HOST_NOT_FOUND => [
'name' => Exception::ROUTER_HOST_NOT_FOUND,
'description' => 'Host is not trusted. This could occur because you have not configured a custom domain. Add a custom domain to your project first and try again.',
@@ -1113,6 +1103,7 @@ return [
'name' => Exception::RULE_VERIFICATION_FAILED,
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
'code' => 400,
'publish' => true
],
Exception::PROJECT_SMTP_CONFIG_INVALID => [
'name' => Exception::PROJECT_SMTP_CONFIG_INVALID,
@@ -1139,11 +1130,6 @@ return [
'description' => 'Key with the requested ID could not be found.',
'code' => 404,
],
Exception::KEY_ALREADY_EXISTS => [
'name' => Exception::KEY_ALREADY_EXISTS,
'description' => 'Key with the same ID already exists. Try again with a different ID.',
'code' => 409,
],
Exception::PLATFORM_NOT_FOUND => [
'name' => Exception::PLATFORM_NOT_FOUND,
'description' => 'Platform with the requested ID could not be found.',
@@ -1343,19 +1329,4 @@ return [
'description' => 'Target has an invalid provider type.',
'code' => 400,
],
Exception::USER_ID_MISSING => [
'name' => Exception::USER_ID_MISSING,
'description' => 'When using account API key, make sure to pass x-appwrite-user header with your user ID.',
'code' => 403,
],
Exception::ORGANIZATION_ID_MISSING => [
'name' => Exception::ORGANIZATION_ID_MISSING,
'description' => 'When using organization API key, make sure to pass x-appwrite-organization header with your organization ID.',
'code' => 403,
],
Exception::PROJECT_ID_MISSING => [
'name' => Exception::PROJECT_ID_MISSING,
'description' => 'When using project API key, make sure to pass x-appwrite-project header with your project ID.',
'code' => 403,
],
];
+38 -21
View File
@@ -50,12 +50,17 @@
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('https://assets.appwrite.io/fonts/poppins/poppins-v23-latin-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
</style>
<style>
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
body {
padding: 32px;
line-height: 1.5;
color: #616b7c;
@@ -63,12 +68,15 @@
font-weight: 400;
font-family: "Inter", sans-serif;
background-color: #ffffff;
margin: 0;
padding: 0;
line-height: 150%;
}
.main a {
a {
color: currentColor;
word-break: break-all;
}
.main a.button {
a.button {
box-sizing: border-box;
display: inline-block;
text-align: center;
@@ -79,8 +87,8 @@
border: 1px solid #414146;
border-radius: 8px;
}
.main a.button:hover,
.main a.button:focus {
a.button:hover,
a.button:focus {
opacity: 0.8;
}
table {
@@ -94,6 +102,11 @@
td {
vertical-align: top;
}
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
}
h1 {
font-size: 22px;
margin-bottom: 0px;
@@ -125,6 +138,9 @@
border: none;
border-top: 1px solid #e8e9f0;
}
h* {
font-family: 'Poppins', sans-serif;
}
p {
margin-bottom: 10px;
}
@@ -139,20 +155,21 @@
<body style="direction: {{direction}}">
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div class="main" style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word; word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
<div style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word;
word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
</body>
</html>
-14
View File
@@ -2,17 +2,6 @@
use Utopia\System\System;
// For now, take first domain as primary (for previews)
// Later-on this can become platform-specific with new env var (appwrite=this,imagine=that)
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (\str_contains($sitesDomain, ',')) {
$sitesDomain = explode(',', $sitesDomain)[0];
}
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (\str_contains($functionsDomain, ',')) {
$functionsDomain = explode(',', $functionsDomain)[0];
}
/**
* Platform configuration
*/
@@ -22,7 +11,6 @@ return [
'hostnames' => array_filter(array_unique([
System::getEnv('_APP_DOMAIN', 'localhost'),
System::getEnv('_APP_CONSOLE_DOMAIN', 'localhost'),
System::getEnv('_APP_MIGRATION_HOST'),
])),
'platformName' => APP_EMAIL_PLATFORM_NAME,
'logoUrl' => APP_EMAIL_LOGO_URL,
@@ -35,6 +23,4 @@ return [
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'websiteUrl' => 'https://' . APP_DOMAIN,
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
'sitesDomain' => $sitesDomain,
'functionsDomain' => $functionsDomain,
];
-15
View File
@@ -1,15 +0,0 @@
<?php
// List of scopes for Account API keys (Tokens)
return [
"account" => [
"description" => 'Access to manage account, its organizations, sessions, tokens, and billing.',
],
"teams.read" => [
"description" => 'Access to read account\'s organizations.',
],
"teams.write" => [
"description" => 'Access to create, update and delete account\'s organizations and its memberships.',
],
];
-42
View File
@@ -1,42 +0,0 @@
<?php
// List of scopes for organization (teams) API keys
return [
"platforms.read" => [
"description" => 'Access to read project\'s platforms',
],
"platforms.write" => [
"description" =>
'Access to create, update, and delete project\'s platforms',
],
"projects.read" => [
"description" => 'Access to read organization\'s projects',
],
"projects.write" => [
"description" =>
"Access to create, update, and delete projects in organization",
],
"keys.read" => [
"description" => 'Access to read project\'s API keys',
],
"keys.write" => [
"description" =>
"Access to create, update, and delete project\'s API keys",
],
"devKeys.read" => [
"description" => 'Access to read project\'s development keys',
],
"devKeys.write" => [
"description" =>
"Access to create, update, and delete project\'s development keys",
],
"webhooks.read" => [
"description" =>
"Access to read project\'s webhooks",
],
"webhooks.write" => [
"description" =>
"Access to create, update, and delete project\'s webhooks",
],
];
+15 -35
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.0.0',
'version' => '21.5.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '21.0.0',
'version' => '20.3.2',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -79,7 +79,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '14.0.0',
'version' => '13.5.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -117,7 +117,7 @@ return [
'key' => 'android',
'name' => 'Android',
'namespace' => 'io.appwrite',
'version' => '12.0.0',
'version' => '11.4.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -140,7 +140,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.20.0',
'version' => '0.19.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -208,7 +208,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.3.0',
'version' => '0.2.0',
'url' => '',
'package' => '',
'enabled' => true,
@@ -227,7 +227,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '13.3.1',
'version' => '12.0.1',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -250,26 +250,6 @@ return [
],
],
],
[
'key' => 'markdown',
'name' => 'Markdown',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-md.git',
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'markdown',
'source' => \realpath(__DIR__ . '/../sdks/console-md'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-md.git',
'gitRepoName' => 'sdk-for-md',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
],
],
],
@@ -283,7 +263,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '22.0.0',
'version' => '21.1.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -302,7 +282,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '20.0.0',
'version' => '19.1.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -321,7 +301,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '15.0.0',
'version' => '14.1.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -340,7 +320,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '21.0.0',
'version' => '20.1.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -378,7 +358,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.25.0',
'version' => '0.24.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -397,7 +377,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '21.0.0',
'version' => '20.1.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -417,7 +397,7 @@ return [
'key' => 'kotlin',
'name' => 'Kotlin',
'namespace' => 'io.appwrite',
'version' => '14.0.0',
'version' => '13.1.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -440,7 +420,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '15.0.0',
'version' => '14.1.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+6 -6
View File
@@ -20,7 +20,7 @@ return [
'name' => 'Console',
'subtitle' => '',
'description' => '',
'controller' => '', // Uses modules
'controller' => 'web/console.php',
'sdk' => false,
'docs' => false,
'docsUrl' => '',
@@ -48,7 +48,7 @@ return [
'name' => 'Avatars',
'subtitle' => 'The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.',
'description' => '/docs/services/avatars.md',
'controller' => '', // Uses modules
'controller' => 'api/avatars.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/avatars',
@@ -104,7 +104,7 @@ return [
'name' => 'Health',
'subtitle' => 'The Health service allows you to both validate and monitor your Appwrite server\'s health.',
'description' => '/docs/services/health.md',
'controller' => '', // Uses modules
'controller' => 'api/health.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/server/health',
@@ -146,7 +146,7 @@ return [
'name' => 'Storage',
'subtitle' => 'The Storage service allows you to manage your project files.',
'description' => '/docs/services/storage.md',
'controller' => '', // Uses modules
'controller' => '',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/storage',
@@ -270,9 +270,9 @@ return [
'console' => [
'key' => 'console',
'name' => 'Console',
'subtitle' => 'The Console service allows you to interact with console relevant information.',
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
'description' => '',
'controller' => '', // Uses modules
'controller' => 'api/console.php',
'sdk' => true,
'docs' => true,
'docsUrl' => '',
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -3,6 +3,4 @@
use Utopia\Image\Image;
use Utopia\System\System;
if (\class_exists('Imagick')) {
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
}
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
+40 -68
View File
@@ -20,34 +20,6 @@ function getRuntimes($runtimes, $commands, $entrypoint, $providerRootDirectory,
}));
}
class FunctionUseCases
{
public const STARTER = 'starter';
public const DATABASES = 'databases';
public const AI = 'ai';
public const MESSAGING = 'messaging';
public const UTILITIES = 'utilities';
public const DEV_TOOLS = 'dev-tools';
public const AUTH = 'auth';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::STARTER,
self::DATABASES,
self::AI,
self::MESSAGING,
self::UTILITIES,
self::DEV_TOOLS,
self::AUTH,
];
}
}
return [
[
'icon' => 'icon-lightning-bolt',
@@ -60,7 +32,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::STARTER],
'useCases' => ['starter'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter', $allowList),
...getRuntimes(
@@ -101,7 +73,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -147,7 +119,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -192,7 +164,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -246,7 +218,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -285,7 +257,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -354,7 +326,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -420,7 +392,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::MESSAGING],
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -487,7 +459,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -525,7 +497,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -576,7 +548,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf', $allowList)
],
@@ -599,7 +571,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DEV_TOOLS],
'useCases' => ['dev-tools'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -644,7 +616,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -697,7 +669,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -782,7 +754,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -881,7 +853,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::MESSAGING],
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -974,7 +946,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::MESSAGING],
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1032,7 +1004,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1119,7 +1091,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1162,7 +1134,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1221,7 +1193,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1257,7 +1229,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1293,7 +1265,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1353,7 +1325,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1413,7 +1385,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1476,7 +1448,7 @@ return [
],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1536,7 +1508,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1573,7 +1545,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1617,7 +1589,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1660,7 +1632,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1697,7 +1669,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1762,7 +1734,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1827,7 +1799,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1884,7 +1856,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1927,7 +1899,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1970,7 +1942,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2014,7 +1986,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2051,7 +2023,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2108,7 +2080,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2181,7 +2153,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AUTH],
'useCases' => ['auth'],
'runtimes' => [
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple', $allowList)
],
+78 -96
View File
@@ -13,7 +13,7 @@ $hostname = $platform['consoleHostname'] ?? '';
$url = $protocol . '://' . $hostname;
class SiteUseCases
class UseCases
{
public const PORTFOLIO = 'portfolio';
public const STARTER = 'starter';
@@ -21,27 +21,9 @@ class SiteUseCases
public const ECOMMERCE = 'ecommerce';
public const DOCUMENTATION = 'documentation';
public const BLOG = 'blog';
public const AI = 'ai';
public const AI = 'artificial intelligence';
public const FORMS = 'forms';
public const DASHBOARD = 'dashboard';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::PORTFOLIO,
self::STARTER,
self::EVENTS,
self::ECOMMERCE,
self::DOCUMENTATION,
self::BLOG,
self::AI,
self::FORMS,
self::DASHBOARD,
];
}
}
const TEMPLATE_FRAMEWORKS = [
@@ -206,7 +188,7 @@ return [
'name' => 'Documentation template',
'tagline' => 'Modern site to store your knowledge with a clean design, full-text search, dark mode, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/template-for-documentation-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-documentation-light.png',
'frameworks' => [
@@ -227,7 +209,7 @@ return [
// When we add Lynx with Appwrite SDK, use following tagline for it:
// 'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-lynx-light.png',
'frameworks' => [
@@ -238,7 +220,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -246,7 +228,7 @@ return [
'name' => 'Vitepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vite.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vitepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vitepress-light.png',
'frameworks' => [
@@ -261,7 +243,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -269,7 +251,7 @@ return [
'name' => 'Vuepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vue.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vuepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vuepress-light.png',
'frameworks' => [
@@ -284,7 +266,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -292,7 +274,7 @@ return [
'name' => 'Docusaurus',
'tagline' => 'Platform for documentation and knowledge sharing powered by React.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/docusaurus-dark.png',
'screenshotLight' => $url . '/images/sites/templates/docusaurus-light.png',
'frameworks' => [
@@ -307,7 +289,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -315,7 +297,7 @@ return [
'name' => 'Nxt Lnk',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/nxt-lnk-dark.png',
'screenshotLight' => $url . '/images/sites/templates/nxt-lnk-light.png',
'frameworks' => [
@@ -326,7 +308,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -334,7 +316,7 @@ return [
'name' => 'Magic Portfolio',
'tagline' => 'Complex personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/magic-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/magic-portfolio-light.png',
'frameworks' => [
@@ -345,7 +327,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -353,7 +335,7 @@ return [
'name' => 'LittleLink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/littlelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/littlelink-light.png',
'frameworks' => [
@@ -364,7 +346,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -372,7 +354,7 @@ return [
'name' => 'Logspot',
'tagline' => 'Website to publish changelogs of your application.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::BLOG],
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/logspot-dark.png',
'screenshotLight' => $url . '/images/sites/templates/logspot-light.png',
'frameworks' => [
@@ -386,7 +368,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -394,7 +376,7 @@ return [
'name' => 'Astro Nano',
'tagline' => 'Minimal personal website to showcase your projects, articles, and more.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-nano-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-nano-light.png',
'frameworks' => [
@@ -407,7 +389,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -415,7 +397,7 @@ return [
'name' => 'Astro Starlight',
'tagline' => 'Platform for documentation and knowledge sharing powered by Astro.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/astro-starlight-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlight-light.png',
'frameworks' => [
@@ -428,7 +410,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -436,7 +418,7 @@ return [
'name' => 'Astro Sphere',
'tagline' => 'Modern personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-sphere-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-sphere-light.png',
'frameworks' => [
@@ -449,7 +431,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -457,7 +439,7 @@ return [
'name' => 'Astro Starlog',
'tagline' => 'Platform for publishing written content and media powered by Astro.',
'score' => 5, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::BLOG],
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/astro-starlog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlog-light.png',
'frameworks' => [
@@ -470,7 +452,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -478,7 +460,7 @@ return [
'name' => 'Onelink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/onelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/onelink-light.png',
'frameworks' => [
@@ -492,13 +474,13 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
'key' => 'starter-for-flutter',
'name' => 'Flutter starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Flutter application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-flutter-dark.png',
@@ -543,7 +525,7 @@ return [
[
'key' => 'starter-for-js',
'name' => 'JavaScript starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple JavaScript application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-js-dark.png',
@@ -587,7 +569,7 @@ return [
[
'key' => 'starter-for-angular',
'name' => 'Angular starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Angular application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-angular-dark.png',
@@ -633,7 +615,7 @@ return [
[
'key' => 'starter-for-astro',
'name' => 'Astro starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Astro application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-astro-dark.png',
@@ -678,7 +660,7 @@ return [
[
'key' => 'starter-for-analog',
'name' => 'Analog starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Analog application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-analog-dark.png',
@@ -722,7 +704,7 @@ return [
[
'key' => 'starter-for-remix',
'name' => 'Remix starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Remix application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-remix-dark.png',
@@ -766,7 +748,7 @@ return [
[
'key' => 'starter-for-svelte',
'name' => 'Svelte starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Svelte application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png',
@@ -810,7 +792,7 @@ return [
[
'key' => 'starter-for-react',
'name' => 'React starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple React application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png',
@@ -854,7 +836,7 @@ return [
[
'key' => 'starter-for-vue',
'name' => 'Vue starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Vue application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png',
@@ -898,7 +880,7 @@ return [
[
'key' => 'starter-for-react-native',
'name' => 'React Native starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple React Native application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
@@ -942,7 +924,7 @@ return [
[
'key' => 'starter-for-nextjs',
'name' => 'Next.js starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Next.js application integrated with Appwrite SDK.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nextjs-dark.png',
@@ -986,7 +968,7 @@ return [
[
'key' => 'starter-for-tanstack-start',
'name' => 'TanStack Start starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.',
'score' => 9, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png',
@@ -1030,7 +1012,7 @@ return [
[
'key' => 'starter-for-nuxt',
'name' => 'Nuxt starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Nuxt application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png',
@@ -1076,7 +1058,7 @@ return [
'name' => 'Event template',
'tagline' => 'Hackathon landing page with support for project submissions.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::EVENTS],
'useCases' => [UseCases::EVENTS],
'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png',
'frameworks' => [
@@ -1114,7 +1096,7 @@ return [
'name' => 'Portfolio template',
'tagline' => 'Simple personal website to showcase your projects, articles, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png',
'frameworks' => [
@@ -1133,7 +1115,7 @@ return [
'name' => 'Store template',
'tagline' => 'E-commerce platform for selling products with Stripe integration.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::ECOMMERCE],
'useCases' => [UseCases::ECOMMERCE],
'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png',
'frameworks' => [
@@ -1177,7 +1159,7 @@ return [
'name' => 'Blog template',
'tagline' => 'Platform for publishing written content and media.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::BLOG],
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png',
'frameworks' => [
@@ -1196,7 +1178,7 @@ return [
'name' => 'Astro playground',
'tagline' => 'A basic Astro website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-astro-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-astro-light.png',
'frameworks' => [
@@ -1207,7 +1189,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1215,7 +1197,7 @@ return [
'name' => 'Remix playground',
'tagline' => 'A basic Remix website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-remix-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-remix-light.png',
'frameworks' => [
@@ -1226,7 +1208,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1234,7 +1216,7 @@ return [
'name' => 'Next.js playground',
'tagline' => 'A basic Next.js website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nextjs-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nextjs-light.png',
'frameworks' => [
@@ -1245,7 +1227,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1253,7 +1235,7 @@ return [
'name' => 'Flutter playground',
'tagline' => 'A basic Flutter website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-flutter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-flutter-light.png',
'frameworks' => [
@@ -1264,7 +1246,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1272,7 +1254,7 @@ return [
'name' => 'Vite playground',
'tagline' => 'A basic Vite website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vite-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vite-light.png',
'frameworks' => [
@@ -1283,7 +1265,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1291,7 +1273,7 @@ return [
'name' => 'Angular playground',
'tagline' => 'A basic Angular website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-angular-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-angular-light.png',
'frameworks' => [
@@ -1303,7 +1285,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1311,7 +1293,7 @@ return [
'name' => 'Analog playground',
'tagline' => 'A basic Analog website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-analog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-analog-light.png',
'frameworks' => [
@@ -1322,7 +1304,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1330,7 +1312,7 @@ return [
'name' => 'Svelte playground',
'tagline' => 'A basic Svelte website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-svelte-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-svelte-light.png',
'frameworks' => [
@@ -1341,7 +1323,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
@@ -1350,7 +1332,7 @@ return [
'name' => 'React playground',
'tagline' => 'A basic React website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-light.png',
'frameworks' => [
@@ -1362,7 +1344,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
@@ -1371,7 +1353,7 @@ return [
'name' => 'Vue playground',
'tagline' => 'A basic Vue website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vue-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vue-light.png',
'frameworks' => [
@@ -1382,7 +1364,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1390,7 +1372,7 @@ return [
'name' => 'Nuxt playground',
'tagline' => 'A basic Nuxt website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nuxt-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nuxt-light.png',
'frameworks' => [
@@ -1401,7 +1383,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1409,7 +1391,7 @@ return [
'name' => 'TanStack Start playground',
'tagline' => 'A basic TanStack Start website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-tanstack-start-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-tanstack-start-light.png',
'frameworks' => [
@@ -1420,7 +1402,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.5.*',
'variables' => [],
],
[
@@ -1428,7 +1410,7 @@ return [
'name' => 'React Native playground',
'tagline' => 'A basic React Native website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-native-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-native-light.png',
'frameworks' => [
@@ -1439,7 +1421,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1447,7 +1429,7 @@ return [
'name' => 'Lynx gallery',
'tagline' => 'A Lynx website showcasing gallery with smooth animations.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/gallery-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/gallery-for-lynx-light.png',
'frameworks' => [
@@ -1458,7 +1440,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -1466,7 +1448,7 @@ return [
'name' => 'Text-to-speech with ElevenLabs',
'tagline' => 'Next.js app that transforms text into natural, human-like speech using ElevenLabs',
'score' => 10, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::AI],
'useCases' => [UseCases::AI],
'screenshotDark' => $url . '/images/sites/templates/text-to-speech-dark.png',
'screenshotLight' => $url . '/images/sites/templates/text-to-speech-light.png',
'frameworks' => [
@@ -1477,7 +1459,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.6.*',
'variables' => [
[
'name' => 'ELEVENLABS_API_KEY',
@@ -1494,7 +1476,7 @@ return [
'name' => 'CRM dashboard with React Admin',
'tagline' => 'A React-based admin dashboard template with CRM features.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DASHBOARD],
'useCases' => [UseCases::DASHBOARD],
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
'frameworks' => [
@@ -1597,7 +1579,7 @@ return [
'name' => 'Job applications form with Formspree',
'tagline' => 'A simple form submission template using Formspree.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::FORMS],
'useCases' => [UseCases::FORMS],
'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png',
'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png',
'frameworks' => [
+9 -9
View File
@@ -369,6 +369,15 @@ return [
]
],
],
[
'name' => '_APP_DB_ADAPTER',
'description' => 'Which database engine to use. Possible options are "mariadb" or "mongodb"',
'introduction' => '1.8.0',
'default' => 'mongoDB',
'required' => true,
'question' => 'Choose your database (mariadb|mongoDB)',
'filter' => ''
],
[
'category' => 'Redis',
'description' => 'Appwrite uses a Redis server for managing cache, queues and scheduled tasks. The Redis env vars are used to allow Appwrite server to connect to the Redis container.',
@@ -1285,15 +1294,6 @@ return [
'category' => 'GraphQL',
'description' => '',
'variables' => [
[
'name' => '_APP_GRAPHQL_INTROSPECTION',
'description' => 'Enable or disable GraphQL introspection. Set to \'enabled\' to allow schema introspection, or \'disabled\' to block it. The default value is \'enabled\'.',
'introduction' => '',
'default' => 'enabled',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_GRAPHQL_MAX_BATCH_SIZE',
'description' => 'Maximum number of batched queries per request. The default value is 10.',
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Domains\Domain;
use Utopia\System\System;
use Utopia\Validator\IP;
use Utopia\Validator\Text;
App::init()
->groups(['console'])
->inject('project')
->action(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
}
});
App::get('/v1/console/variables')
->desc('Get variables')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'console',
group: 'console',
name: 'variables',
description: '/docs/references/console/variables.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_CONSOLE_VARIABLES,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
$validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME'));
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
$validator = new IP(IP::V4);
$isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A')));
$validator = new IP(IP::V6);
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
$isDomainEnabled = $isAAAAValid || $isAValid || $isCNAMEValid;
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''));
$isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', ''));
$variables = new Document([
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
// Combine CAA domain with most common flags and tag (no parameters)
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
'_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'),
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
'_APP_VCS_ENABLED' => $isVcsEnabled,
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
]);
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
});
App::post('/v1/console/assistant')
->desc('Create assistant query')
->groups(['api', 'assistant'])
->label('scope', 'assistant.read')
->label('sdk', new Method(
namespace: 'assistant',
group: 'console',
name: 'chat',
description: '/docs/references/assistant/chat.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::TEXT
))
->label('abuse-limit', 15)
->label('abuse-key', 'userId:{userId}')
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
->inject('response')
->action(function (string $prompt, Response $response) {
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
$responseHeaders = [];
$query = json_encode(['prompt' => $prompt]);
$headers = ['accept: text/event-stream'];
$handleEvent = function ($ch, $data) use ($response) {
$response->chunk($data);
return \strlen($data);
};
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);
if (count($header) < 2) { // ignore invalid headers
return $len;
}
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
});
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
curl_exec($ch);
curl_close($ch);
$response->chunk('', true);
});
+10 -14
View File
@@ -18,28 +18,27 @@ use GraphQL\Validator\Rules\DisableIntrospection;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\QueryDepth;
use Swoole\Coroutine\WaitGroup;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Http;
use Utopia\System\System;
use Utopia\Validator\JSON;
use Utopia\Validator\Text;
Http::init()
App::init()
->groups(['graphql'])
->inject('project')
->inject('authorization')
->action(function (Document $project, Authorization $authorization) {
->action(function (Document $project) {
if (
array_key_exists('graphql', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['graphql']
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
});
Http::get('/v1/graphql')
App::get('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -86,7 +85,7 @@ Http::get('/v1/graphql')
->json($output);
});
Http::post('/v1/graphql/mutation')
App::post('/v1/graphql/mutation')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -137,7 +136,7 @@ Http::post('/v1/graphql/mutation')
->json($output);
});
Http::post('/v1/graphql')
App::post('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -224,15 +223,12 @@ function execute(
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
$validations = GraphQL::getStandardValidationRules();
if (System::getEnv('_APP_GRAPHQL_INTROSPECTION', 'enabled') === 'disabled') {
$validations[] = new DisableIntrospection();
}
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
$validations[] = new DisableIntrospection();
$validations[] = new QueryComplexity($maxComplexity);
$validations[] = new QueryDepth($maxDepth);
}
if (Http::getMode() === Http::MODE_TYPE_PRODUCTION) {
if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
$flags = DebugFlag::NONE;
}
@@ -333,7 +329,7 @@ function processResult($result, $debugFlags): array
);
}
Http::shutdown()
App::shutdown()
->groups(['schema'])
->inject('project')
->action(function (Document $project) {
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -6,12 +6,12 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Http;
use Utopia\Locale\Locale;
Http::get('/v1/locale')
App::get('/v1/locale')
->desc('Get user locale')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -70,7 +70,7 @@ Http::get('/v1/locale')
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
});
Http::get('/v1/locale/codes')
App::get('/v1/locale/codes')
->desc('List locale codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -96,7 +96,7 @@ Http::get('/v1/locale/codes')
]), Response::MODEL_LOCALE_CODE_LIST);
});
Http::get('/v1/locale/countries')
App::get('/v1/locale/countries')
->desc('List countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -133,7 +133,7 @@ Http::get('/v1/locale/countries')
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
});
Http::get('/v1/locale/countries/eu')
App::get('/v1/locale/countries/eu')
->desc('List EU countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -172,7 +172,7 @@ Http::get('/v1/locale/countries/eu')
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
});
Http::get('/v1/locale/countries/phones')
App::get('/v1/locale/countries/phones')
->desc('List countries phone codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -210,7 +210,7 @@ Http::get('/v1/locale/countries/phones')
$response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST);
});
Http::get('/v1/locale/continents')
App::get('/v1/locale/continents')
->desc('List continents')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -246,7 +246,7 @@ Http::get('/v1/locale/continents')
$response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST);
});
Http::get('/v1/locale/currencies')
App::get('/v1/locale/currencies')
->desc('List currencies')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -273,7 +273,7 @@ Http::get('/v1/locale/currencies')
});
Http::get('/v1/locale/languages')
App::get('/v1/locale/languages')
->desc('List languages')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
File diff suppressed because it is too large Load Diff
+66 -40
View File
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CompoundUID;
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
@@ -21,7 +22,6 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries\Documents;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\CSV;
@@ -43,7 +43,15 @@ use Utopia\Validator\WhiteList;
include_once __DIR__ . '/../shared/api.php';
Http::post('/v1/migrations/appwrite')
function getDatabaseTransferResourceServices(string $databaseType)
{
return match($databaseType) {
DATABASE_TYPE_LEGACY,
DATABASE_TYPE_TABLESDB => Transfer::GROUP_DATABASES_TABLES_DB
};
}
App::post('/v1/migrations/appwrite')
->groups(['api', 'migrations'])
->desc('Create Appwrite migration')
->label('scope', 'migrations.write')
@@ -64,7 +72,7 @@ Http::post('/v1/migrations/appwrite')
))
->param('resources', [], new ArrayList(new WhiteList(Appwrite::getSupportedResources())), 'List of resources to migrate')
->param('endpoint', '', new URL(), 'Source Appwrite endpoint')
->param('projectId', '', new UID(), 'Source Project ID')
->param('projectId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Source Project ID', false, ['dbForProject'])
->param('apiKey', '', new Text(512), 'Source API Key')
->inject('response')
->inject('dbForProject')
@@ -106,7 +114,7 @@ Http::post('/v1/migrations/appwrite')
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/firebase')
App::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
->desc('Create Firebase migration')
->label('scope', 'migrations.write')
@@ -175,7 +183,7 @@ Http::post('/v1/migrations/firebase')
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/supabase')
App::post('/v1/migrations/supabase')
->groups(['api', 'migrations'])
->desc('Create Supabase migration')
->label('scope', 'migrations.write')
@@ -244,7 +252,7 @@ Http::post('/v1/migrations/supabase')
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/nhost')
App::post('/v1/migrations/nhost')
->groups(['api', 'migrations'])
->desc('Create NHost migration')
->label('scope', 'migrations.write')
@@ -315,7 +323,7 @@ Http::post('/v1/migrations/nhost')
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/csv/imports')
App::post('/v1/migrations/csv/imports')
->alias('/v1/migrations/csv')
->groups(['api', 'migrations'])
->desc('Import documents from a CSV')
@@ -335,14 +343,13 @@ Http::post('/v1/migrations/csv/imports')
)
]
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->param('bucketId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).', false, ['dbForProject'])
->param('fileId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'File ID.', false, ['dbForProject'])
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('deviceForFiles')
@@ -357,7 +364,6 @@ Http::post('/v1/migrations/csv/imports')
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Device $deviceForFiles,
@@ -365,7 +371,7 @@ Http::post('/v1/migrations/csv/imports')
Event $queueForEvents,
Migration $queueForMigrations
) {
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
if ($internalFile) {
return $dbForPlatform->getDocument('buckets', 'default');
}
@@ -376,7 +382,7 @@ Http::post('/v1/migrations/csv/imports')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
@@ -427,8 +433,16 @@ Http::post('/v1/migrations/csv/imports')
throw new \Exception('Unable to copy file');
}
// getting databasetype
$resources = explode(':', $resourceId);
$databaseId = $resources[0];
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$databaseType = $database->getAttribute('type');
if (!in_array($databaseType, CSV_ALLOWED_DATABASE_TYPES)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Database type not supported for csv');
}
$fileSize = $deviceForMigrations->getFileSize($newPath);
$resources = Transfer::extractServices([Transfer::GROUP_DATABASES]);
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => $migrationId,
@@ -461,7 +475,7 @@ Http::post('/v1/migrations/csv/imports')
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::post('/v1/migrations/csv/exports')
App::post('/v1/migrations/csv/exports')
->groups(['api', 'migrations'])
->desc('Export documents to CSV')
->label('scope', 'migrations.write')
@@ -493,7 +507,6 @@ Http::post('/v1/migrations/csv/exports')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('queueForEvents')
@@ -512,7 +525,6 @@ Http::post('/v1/migrations/csv/exports')
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Event $queueForEvents,
@@ -524,7 +536,7 @@ Http::post('/v1/migrations/csv/exports')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -537,12 +549,12 @@ Http::post('/v1/migrations/csv/exports')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
@@ -557,13 +569,23 @@ Http::post('/v1/migrations/csv/exports')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
// getting databasetype
$resources = explode(':', $resourceId);
$databaseId = $resources[0];
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$databaseType = $database->getAttribute('type');
if (!in_array($databaseType, CSV_ALLOWED_DATABASE_TYPES)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Database type not supported for csv');
}
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
'stage' => 'init',
'source' => Appwrite::getName(),
'destination' => CSV::getName(),
'resources' => Transfer::extractServices([Transfer::GROUP_DATABASES]),
'resources' => $resources,
'resourceId' => $resourceId,
'resourceType' => Resource::TYPE_DATABASE,
'statusCounters' => '{}',
@@ -596,7 +618,7 @@ Http::post('/v1/migrations/csv/exports')
->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::get('/v1/migrations')
App::get('/v1/migrations')
->groups(['api', 'migrations'])
->desc('List migrations')
->label('scope', 'migrations.read')
@@ -629,10 +651,16 @@ Http::get('/v1/migrations')
$queries[] = Query::search('search', $search);
}
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -661,7 +689,7 @@ Http::get('/v1/migrations')
]), Response::MODEL_MIGRATION_LIST);
});
Http::get('/v1/migrations/:migrationId')
App::get('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Get migration')
->label('scope', 'migrations.read')
@@ -678,7 +706,7 @@ Http::get('/v1/migrations/:migrationId')
)
]
))
->param('migrationId', '', new UID(), 'Migration unique ID.')
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $migrationId, Response $response, Database $dbForProject) {
@@ -691,7 +719,7 @@ Http::get('/v1/migrations/:migrationId')
$response->dynamic($migration, Response::MODEL_MIGRATION);
});
Http::get('/v1/migrations/appwrite/report')
App::get('/v1/migrations/appwrite/report')
->groups(['api', 'migrations'])
->desc('Get Appwrite migration report')
->label('scope', 'migrations.write')
@@ -713,12 +741,10 @@ Http::get('/v1/migrations/appwrite/report')
->param('projectID', '', new Text(512), "Source's Project ID")
->param('key', '', new Text(512), "Source's API Key")
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) {
->inject('getDatabasesDB')
->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response, callable $getDatabasesDB) {
$appwrite = new Appwrite($projectID, $endpoint, $key);
$appwrite = new Appwrite($projectID, $endpoint, $key, $getDatabasesDB);
try {
$report = $appwrite->report($resources);
@@ -740,7 +766,7 @@ Http::get('/v1/migrations/appwrite/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::get('/v1/migrations/firebase/report')
App::get('/v1/migrations/firebase/report')
->groups(['api', 'migrations'])
->desc('Get Firebase migration report')
->label('scope', 'migrations.write')
@@ -793,7 +819,7 @@ Http::get('/v1/migrations/firebase/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::get('/v1/migrations/supabase/report')
App::get('/v1/migrations/supabase/report')
->groups(['api', 'migrations'])
->desc('Get Supabase migration report')
->label('scope', 'migrations.write')
@@ -842,7 +868,7 @@ Http::get('/v1/migrations/supabase/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::get('/v1/migrations/nhost/report')
App::get('/v1/migrations/nhost/report')
->groups(['api', 'migrations'])
->desc('Get NHost migration report')
->label('scope', 'migrations.write')
@@ -891,7 +917,7 @@ Http::get('/v1/migrations/nhost/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
Http::patch('/v1/migrations/:migrationId')
App::patch('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Update retry migration')
->label('scope', 'migrations.write')
@@ -911,7 +937,7 @@ Http::patch('/v1/migrations/:migrationId')
)
]
))
->param('migrationId', '', new UID(), 'Migration unique ID.')
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('project')
@@ -944,7 +970,7 @@ Http::patch('/v1/migrations/:migrationId')
$response->noContent();
});
Http::delete('/v1/migrations/:migrationId')
App::delete('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
->desc('Delete migration')
->label('scope', 'migrations.write')
@@ -965,7 +991,7 @@ Http::delete('/v1/migrations/:migrationId')
],
contentType: ContentType::NONE
))
->param('migrationId', '', new UID(), 'Migration ID.')
->param('migrationId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Migration ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
+30 -15
View File
@@ -6,6 +6,7 @@ use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
@@ -16,13 +17,12 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
Http::get('/v1/project/usage')
App::get('/v1/project/usage')
->desc('Get project usage stats')
->groups(['api', 'usage'])
->label('scope', 'projects.read')
@@ -45,10 +45,9 @@ Http::get('/v1/project/usage')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->inject('getLogsDB')
->inject('smsRates')
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, Authorization $authorization, callable $getLogsDB, array $smsRates) {
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
@@ -62,15 +61,20 @@ Http::get('/v1/project/usage')
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS,
METRIC_DOCUMENTS,
METRIC_DOCUMENTS_DOCUMENTSDB,
METRIC_DATABASES,
METRIC_DATABASES_DOCUMENTSDB,
METRIC_USERS,
METRIC_BUCKETS,
METRIC_FILES_STORAGE,
METRIC_DATABASES_STORAGE,
METRIC_DATABASES_STORAGE_DOCUMENTSDB,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS_STORAGE,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB,
METRIC_DATABASES_OPERATIONS_WRITES,
METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB,
METRIC_FILES_IMAGES_TRANSFORMED,
],
'period' => [
@@ -80,10 +84,13 @@ Http::get('/v1/project/usage')
METRIC_USERS,
METRIC_EXECUTIONS,
METRIC_DATABASES_STORAGE,
METRIC_DATABASES_STORAGE_DOCUMENTSDB,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB,
METRIC_DATABASES_OPERATIONS_WRITES,
METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB,
METRIC_FILES_IMAGES_TRANSFORMED,
]
];
@@ -103,7 +110,7 @@ Http::get('/v1/project/usage')
'1d' => 'Y-m-d\T00:00:00.000P',
};
$authorization->skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
foreach ($metrics['total'] as $metric) {
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
@@ -287,7 +294,7 @@ Http::get('/v1/project/usage')
}, $dbForProject->find('functions'));
// This total is includes free and paid SMS usage
$authPhoneTotal = $authorization->skip(fn () => $dbForProject->sum('stats', 'value', [
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay),
@@ -295,7 +302,7 @@ Http::get('/v1/project/usage')
]));
// This estimate is only for paid SMS usage
$authPhoneMetrics = $authorization->skip(fn () => $dbForProject->find('stats', [
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay),
@@ -357,8 +364,11 @@ Http::get('/v1/project/usage')
'buildsMbSecondsTotal' => $total[METRIC_BUILDS_MB_SECONDS],
'documentsTotal' => $total[METRIC_DOCUMENTS],
'rowsTotal' => $total[METRIC_DOCUMENTS],
'documentsdbDocumentsTotal' => $total[METRIC_DOCUMENTS_DOCUMENTSDB],
'databasesTotal' => $total[METRIC_DATABASES],
'documentsdbTotal' => $total[METRIC_DATABASES_DOCUMENTSDB],
'databasesStorageTotal' => $total[METRIC_DATABASES_STORAGE],
'documentsdbDatabasesStorageTotal' => $total[METRIC_DATABASES_STORAGE_DOCUMENTSDB],
'usersTotal' => $total[METRIC_USERS],
'bucketsTotal' => $total[METRIC_BUCKETS],
'filesStorageTotal' => $total[METRIC_FILES_STORAGE],
@@ -367,10 +377,15 @@ Http::get('/v1/project/usage')
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
'databasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS],
'databasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES],
'documentsdbDatabasesReadsTotal' => $total[METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB],
'documentsdbDatabasesWritesTotal' => $total[METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB],
'executionsBreakdown' => $executionsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown,
'databasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS],
'databasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES],
'documentsdbDatabasesReads' => $usage[METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB],
'documentsdbDatabasesWrites' => $usage[METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB],
'documentsdbDatabasesStorage' => $usage[METRIC_DATABASES_STORAGE_DOCUMENTSDB],
'databasesStorageBreakdown' => $databasesStorageBreakdown,
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
@@ -385,7 +400,7 @@ Http::get('/v1/project/usage')
// Variables
Http::post('/v1/project/variables')
App::post('/v1/project/variables')
->desc('Create variable')
->groups(['api'])
->label('scope', 'projects.write')
@@ -448,7 +463,7 @@ Http::post('/v1/project/variables')
->dynamic($variable, Response::MODEL_VARIABLE);
});
Http::get('/v1/project/variables')
App::get('/v1/project/variables')
->desc('List variables')
->groups(['api'])
->label('scope', 'projects.read')
@@ -479,7 +494,7 @@ Http::get('/v1/project/variables')
]), Response::MODEL_VARIABLE_LIST);
});
Http::get('/v1/project/variables/:variableId')
App::get('/v1/project/variables/:variableId')
->desc('Get variable')
->groups(['api'])
->label('scope', 'projects.read')
@@ -496,7 +511,7 @@ Http::get('/v1/project/variables/:variableId')
)
]
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->inject('response')
->inject('project')
->inject('dbForProject')
@@ -509,7 +524,7 @@ Http::get('/v1/project/variables/:variableId')
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
Http::put('/v1/project/variables/:variableId')
App::put('/v1/project/variables/:variableId')
->desc('Update variable')
->groups(['api'])
->label('scope', 'projects.write')
@@ -526,7 +541,7 @@ Http::put('/v1/project/variables/:variableId')
)
]
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Nullable(new Text(8192, 0)), 'Variable value. Max length: 8192 chars.', true)
->param('secret', null, new Nullable(new Boolean()), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
@@ -567,7 +582,7 @@ Http::put('/v1/project/variables/:variableId')
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
Http::delete('/v1/project/variables/:variableId')
App::delete('/v1/project/variables/:variableId')
->desc('Delete variable')
->groups(['api'])
->label('scope', 'projects.write')
@@ -585,7 +600,7 @@ Http::delete('/v1/project/variables/:variableId')
],
contentType: ContentType::NONE
))
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('variableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Variable unique ID.', false, ['dbForProject'])
->inject('project')
->inject('response')
->inject('dbForProject')
File diff suppressed because it is too large Load Diff
View File
+127 -109
View File
@@ -23,9 +23,10 @@ use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Teams;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
@@ -51,15 +52,15 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
Http::post('/v1/teams')
App::post('/v1/teams')
->desc('Create team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].create')
@@ -79,30 +80,28 @@ Http::post('/v1/teams')
)
]
))
->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('teamId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
->param('roles', ['owner'], fn (Database $dbForProject) => new ArrayList(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true, ['dbForProject'])
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents')
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isAppUser = User::isApp(Authorization::getRoles());
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
try {
$team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
$team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([
'$id' => $teamId,
'$permissions' => [
Permission::read(Role::team($teamId)),
Permission::update(Role::team($teamId, 'owner')),
Permission::delete(Role::team($teamId, 'owner')),
],
'labels' => [],
'name' => $name,
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
'prefs' => new \stdClass(),
@@ -155,7 +154,7 @@ Http::post('/v1/teams')
->dynamic($team, Response::MODEL_TEAM);
});
Http::get('/v1/teams')
App::get('/v1/teams')
->desc('List teams')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -190,10 +189,16 @@ Http::get('/v1/teams')
$queries[] = Query::search('search', $search);
}
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -223,7 +228,7 @@ Http::get('/v1/teams')
]), Response::MODEL_TEAM_LIST);
});
Http::get('/v1/teams/:teamId')
App::get('/v1/teams/:teamId')
->desc('Get team')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -240,7 +245,7 @@ Http::get('/v1/teams/:teamId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $teamId, Response $response, Database $dbForProject) {
@@ -254,7 +259,7 @@ Http::get('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
Http::get('/v1/teams/:teamId/prefs')
App::get('/v1/teams/:teamId/prefs')
->desc('Get team preferences')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -271,7 +276,7 @@ Http::get('/v1/teams/:teamId/prefs')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $teamId, Response $response, Database $dbForProject) {
@@ -293,7 +298,7 @@ Http::get('/v1/teams/:teamId/prefs')
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
});
Http::put('/v1/teams/:teamId')
App::put('/v1/teams/:teamId')
->desc('Update name')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update')
@@ -313,7 +318,7 @@ Http::put('/v1/teams/:teamId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
->inject('requestTimestamp')
->inject('response')
@@ -338,7 +343,7 @@ Http::put('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
Http::put('/v1/teams/:teamId/prefs')
App::put('/v1/teams/:teamId/prefs')
->desc('Update preferences')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update.prefs')
@@ -359,7 +364,7 @@ Http::put('/v1/teams/:teamId/prefs')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('dbForProject')
@@ -386,7 +391,7 @@ Http::put('/v1/teams/:teamId/prefs')
$response->dynamic($prefs, Response::MODEL_PREFERENCES);
});
Http::delete('/v1/teams/:teamId')
App::delete('/v1/teams/:teamId')
->desc('Delete team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].delete')
@@ -407,7 +412,7 @@ Http::delete('/v1/teams/:teamId')
],
contentType: ContentType::NONE
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->inject('response')
->inject('getProjectDB')
->inject('dbForProject')
@@ -426,22 +431,15 @@ Http::delete('/v1/teams/:teamId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
}
// Sync delete
$deletes = new Deletes();
$deletes->deleteMemberships($getProjectDB, $team, $project);
// Async delete
if ($project->getId() === 'console') {
$queueForDeletes
->setType(DELETE_TYPE_TEAM_PROJECTS)
->setDocument($team)
->trigger();
->setDocument($team);
}
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($team);
$queueForEvents
->setParam('teamId', $team->getId())
->setPayload($response->output($team, Response::MODEL_TEAM))
@@ -450,7 +448,7 @@ Http::delete('/v1/teams/:teamId')
$response->noContent();
});
Http::post('/v1/teams/:teamId/memberships')
App::post('/v1/teams/:teamId/memberships')
->desc('Create team membership')
->groups(['api', 'teams', 'auth'])
->label('event', 'teams.[teamId].memberships.[membershipId].create')
@@ -473,18 +471,26 @@ Http::post('/v1/teams/:teamId/memberships')
]
))
->label('abuse-limit', 10)
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'Email of the new team member.', true)
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'ID of the user to be added to a team.', true, ['dbForProject'])
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
->param('roles', [], function (Document $project, Database $dbForProject) {
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
$roles = array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return new ArrayList(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project', 'dbForProject'])
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('user')
->inject('dbForProject')
->inject('authorization')
->inject('locale')
->inject('queueForMails')
->inject('queueForMessaging')
@@ -494,9 +500,9 @@ Http::post('/v1/teams/:teamId/memberships')
->inject('plan')
->inject('proofForPassword')
->inject('proofForToken')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
$isAppUser = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
$isAppUser = User::isApp(Authorization::getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$url = htmlentities($url);
if (empty($url)) {
@@ -613,13 +619,13 @@ Http::post('/v1/teams/:teamId/memberships')
]);
try {
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', $userDocument));
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', $userDocument));
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
}
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
@@ -655,11 +661,11 @@ Http::post('/v1/teams/:teamId/memberships')
]);
$membership = ($isPrivilegedUser || $isAppUser) ?
$authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
$dbForProject->createDocument('memberships', $membership);
if ($isPrivilegedUser || $isAppUser) {
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
}
} elseif ($membership->getAttribute('confirm') === false) {
$membership->setAttribute('secret', $proofForToken->hash($secret));
@@ -671,7 +677,7 @@ Http::post('/v1/teams/:teamId/memberships')
}
$membership = ($isPrivilegedUser || $isAppUser) ?
$authorization->skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
Authorization::skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
$dbForProject->updateDocument('memberships', $membership->getId(), $membership);
} else {
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
@@ -763,7 +769,7 @@ Http::post('/v1/teams/:teamId/memberships')
->setPreview($preview)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name', ''))
->appendVariables($emailVariables)
->setVariables($emailVariables)
->trigger();
} elseif (!empty($phone)) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
@@ -793,21 +799,26 @@ Http::post('/v1/teams/:teamId/memberships')
->setRecipients([$phone])
->setProviderType('SMS');
$helper = PhoneNumberUtil::getInstance();
try {
$countryCode = $helper->parse($phone)->getCountryCode();
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
}
} catch (NumberParseException $e) {
// Ignore invalid phone number for country code stats
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
}
@@ -828,7 +839,7 @@ Http::post('/v1/teams/:teamId/memberships')
);
});
Http::get('/v1/teams/:teamId/memberships')
App::get('/v1/teams/:teamId/memberships')
->desc('List team memberships')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -845,15 +856,14 @@ Http::get('/v1/teams/:teamId/memberships')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -873,15 +883,22 @@ Http::get('/v1/teams/:teamId/memberships')
// Set internal queries
$queries[] = Query::equal('teamInternalId', [$team->getSequence()]);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$membershipId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('memberships', $membershipId);
@@ -916,7 +933,7 @@ Http::get('/v1/teams/:teamId/memberships')
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$roles = $authorization->getRoles();
$roles = Authorization::getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
@@ -965,7 +982,7 @@ Http::get('/v1/teams/:teamId/memberships')
]), Response::MODEL_MEMBERSHIP_LIST);
});
Http::get('/v1/teams/:teamId/memberships/:membershipId')
App::get('/v1/teams/:teamId/memberships/:membershipId')
->desc('Get team membership')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -982,13 +999,12 @@ Http::get('/v1/teams/:teamId/memberships/:membershipId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -1008,7 +1024,7 @@ Http::get('/v1/teams/:teamId/memberships/:membershipId')
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$roles = $authorization->getRoles();
$roles = Authorization::getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
@@ -1050,7 +1066,7 @@ Http::get('/v1/teams/:teamId/memberships/:membershipId')
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
});
Http::patch('/v1/teams/:teamId/memberships/:membershipId')
App::patch('/v1/teams/:teamId/memberships/:membershipId')
->desc('Update membership')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].update')
@@ -1070,17 +1086,25 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->param('roles', [], function (Document $project, Database $dbForProject) {
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
$roles = array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
return new ArrayList(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE);
}, 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project', 'dbForProject'])
->inject('request')
->inject('response')
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -1097,9 +1121,9 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::USER_NOT_FOUND);
}
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isAppUser = User::isApp(Authorization::getRoles());
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
if ($project->getId() === 'console') {
// Quick check: fetch up to 2 owners to determine if only one exists
@@ -1151,7 +1175,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
);
});
Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->desc('Update team membership status')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].update.status')
@@ -1172,21 +1196,20 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('userId', '', new UID(), 'User ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('secret', '', new Text(256), 'Secret key.')
->inject('request')
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('authorization')
->inject('project')
->inject('geodb')
->inject('queueForEvents')
->inject('store')
->inject('proofForToken')
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1195,7 +1218,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
$team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
$team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId));
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
@@ -1231,11 +1254,11 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setAttribute('confirm', true)
;
$authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
// Create session for the user if not logged in
if (!$hasSession) {
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
@@ -1263,7 +1286,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$session = $dbForProject->createDocument('sessions', $session);
$authorization->addRole(Role::user($userId)->toString());
Authorization::setRole(Role::user($userId)->toString());
$encoded = $store
->setProperty('id', $user->getId())
@@ -1301,7 +1324,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$dbForProject->purgeCachedDocument('users', $user->getId());
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$queueForEvents
->setParam('userId', $user->getId())
@@ -1318,7 +1341,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
);
});
Http::delete('/v1/teams/:teamId/memberships/:membershipId')
App::delete('/v1/teams/:teamId/memberships/:membershipId')
->desc('Delete team membership')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].delete')
@@ -1339,15 +1362,14 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
],
contentType: ContentType::NONE
))
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('membershipId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Membership ID.', false, ['dbForProject'])
->inject('user')
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) {
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1405,7 +1427,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
$dbForProject->purgeCachedDocument('users', $profile->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
}
$queueForEvents
@@ -1418,7 +1440,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
$response->noContent();
});
Http::get('/v1/teams/:teamId/logs')
App::get('/v1/teams/:teamId/logs')
->desc('List team logs')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -1435,15 +1457,14 @@ Http::get('/v1/teams/:teamId/logs')
)
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('teamId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Team ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -1457,12 +1478,9 @@ Http::get('/v1/teams/:teamId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'team/' . $team->getId();
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -1509,7 +1527,7 @@ Http::get('/v1/teams/:teamId/logs')
}
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+125 -116
View File
@@ -29,6 +29,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Hash;
use Utopia\Auth\Hashes\Argon2;
@@ -60,7 +61,6 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -227,7 +227,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor
return $user;
}
Http::post('/v1/users')
App::post('/v1/users')
->desc('Create user')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -246,7 +246,7 @@ Http::post('/v1/users')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', null, new Nullable(new EmailValidator()), 'User email.', true)
->param('phone', null, new Nullable(new Phone()), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
@@ -264,7 +264,7 @@ Http::post('/v1/users')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/bcrypt')
App::post('/v1/users/bcrypt')
->desc('Create user with bcrypt password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -283,7 +283,7 @@ Http::post('/v1/users/bcrypt')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -302,7 +302,7 @@ Http::post('/v1/users/bcrypt')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/md5')
App::post('/v1/users/md5')
->desc('Create user with MD5 password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -321,7 +321,7 @@ Http::post('/v1/users/md5')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using MD5.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -339,7 +339,7 @@ Http::post('/v1/users/md5')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/argon2')
App::post('/v1/users/argon2')
->desc('Create user with Argon2 password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -358,7 +358,7 @@ Http::post('/v1/users/argon2')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Argon2.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -376,7 +376,7 @@ Http::post('/v1/users/argon2')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/sha')
App::post('/v1/users/sha')
->desc('Create user with SHA password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -395,7 +395,7 @@ Http::post('/v1/users/sha')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using SHA.')
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
@@ -417,7 +417,7 @@ Http::post('/v1/users/sha')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/phpass')
App::post('/v1/users/phpass')
->desc('Create user with PHPass password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -436,7 +436,7 @@ Http::post('/v1/users/phpass')
)
]
))
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using PHPass.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -454,7 +454,7 @@ Http::post('/v1/users/phpass')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/scrypt')
App::post('/v1/users/scrypt')
->desc('Create user with Scrypt password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -473,7 +473,7 @@ Http::post('/v1/users/scrypt')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt.')
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
@@ -502,7 +502,7 @@ Http::post('/v1/users/scrypt')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/scrypt-modified')
App::post('/v1/users/scrypt-modified')
->desc('Create user with Scrypt modified password')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -521,7 +521,7 @@ Http::post('/v1/users/scrypt-modified')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using Scrypt Modified.')
->param('passwordSalt', '', new Text(128), 'Salt used to hash password.')
@@ -546,7 +546,7 @@ Http::post('/v1/users/scrypt-modified')
->dynamic($user, Response::MODEL_USER);
});
Http::post('/v1/users/:userId/targets')
App::post('/v1/users/:userId/targets')
->desc('Create user target')
->groups(['api', 'users'])
->label('audits.event', 'target.create')
@@ -566,11 +566,11 @@ Http::post('/v1/users/:userId/targets')
)
]
))
->param('targetId', '', new CustomId(), 'Target 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('userId', '', new UID(), 'User ID.')
->param('targetId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Target 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.', false, ['dbForProject'])
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true, ['dbForProject'])
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
@@ -641,7 +641,7 @@ Http::post('/v1/users/:userId/targets')
->dynamic($target, Response::MODEL_TARGET);
});
Http::get('/v1/users')
App::get('/v1/users')
->desc('List users')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -675,10 +675,16 @@ Http::get('/v1/users')
$queries[] = Query::search('search', $search);
}
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -714,7 +720,7 @@ Http::get('/v1/users')
]), Response::MODEL_USER_LIST);
});
Http::get('/v1/users/:userId')
App::get('/v1/users/:userId')
->desc('Get user')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -731,7 +737,7 @@ Http::get('/v1/users/:userId')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@@ -745,7 +751,7 @@ Http::get('/v1/users/:userId')
$response->dynamic($user, Response::MODEL_USER);
});
Http::get('/v1/users/:userId/prefs')
App::get('/v1/users/:userId/prefs')
->desc('Get user preferences')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -762,7 +768,7 @@ Http::get('/v1/users/:userId/prefs')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@@ -778,7 +784,7 @@ Http::get('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
Http::get('/v1/users/:userId/targets/:targetId')
App::get('/v1/users/:userId/targets/:targetId')
->desc('Get user target')
->groups(['api', 'users'])
->label('scope', 'targets.read')
@@ -795,8 +801,8 @@ Http::get('/v1/users/:userId/targets/:targetId')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $targetId, Response $response, Database $dbForProject) {
@@ -816,7 +822,7 @@ Http::get('/v1/users/:userId/targets/:targetId')
$response->dynamic($target, Response::MODEL_TARGET);
});
Http::get('/v1/users/:userId/sessions')
App::get('/v1/users/:userId/sessions')
->desc('List user sessions')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -833,7 +839,7 @@ Http::get('/v1/users/:userId/sessions')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
@@ -860,7 +866,7 @@ Http::get('/v1/users/:userId/sessions')
]), Response::MODEL_SESSION_LIST);
});
Http::get('/v1/users/:userId/memberships')
App::get('/v1/users/:userId/memberships')
->desc('List user memberships')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -877,7 +883,7 @@ Http::get('/v1/users/:userId/memberships')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
@@ -915,7 +921,7 @@ Http::get('/v1/users/:userId/memberships')
]), Response::MODEL_MEMBERSHIP_LIST);
});
Http::get('/v1/users/:userId/logs')
App::get('/v1/users/:userId/logs')
->desc('List user logs')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -932,15 +938,14 @@ Http::get('/v1/users/:userId/logs')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$user = $dbForProject->getDocument('users', $userId);
@@ -953,11 +958,8 @@ Http::get('/v1/users/:userId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), limit: $limit, offset: $offset);
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
@@ -997,12 +999,12 @@ Http::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
Http::get('/v1/users/:userId/targets')
App::get('/v1/users/:userId/targets')
->desc('List user targets')
->groups(['api', 'users'])
->label('scope', 'targets.read')
@@ -1019,7 +1021,7 @@ Http::get('/v1/users/:userId/targets')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Targets::ALLOWED_ATTRIBUTES), true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
@@ -1036,11 +1038,14 @@ Http::get('/v1/users/:userId/targets')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries[] = Query::equal('userId', [$userId]);
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -1064,7 +1069,7 @@ Http::get('/v1/users/:userId/targets')
]), Response::MODEL_TARGET_LIST);
});
Http::get('/v1/users/identities')
App::get('/v1/users/identities')
->desc('List identities')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -1097,11 +1102,15 @@ Http::get('/v1/users/identities')
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
@@ -1126,7 +1135,7 @@ Http::get('/v1/users/identities')
]), Response::MODEL_IDENTITY_LIST);
});
Http::patch('/v1/users/:userId/status')
App::patch('/v1/users/:userId/status')
->desc('Update user status')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.status')
@@ -1147,7 +1156,7 @@ Http::patch('/v1/users/:userId/status')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('status', null, new Boolean(true), 'User Status. To activate the user pass `true` and to block the user pass `false`.')
->inject('response')
->inject('dbForProject')
@@ -1168,7 +1177,7 @@ Http::patch('/v1/users/:userId/status')
$response->dynamic($user, Response::MODEL_USER);
});
Http::put('/v1/users/:userId/labels')
App::put('/v1/users/:userId/labels')
->desc('Update user labels')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.labels')
@@ -1188,7 +1197,7 @@ Http::put('/v1/users/:userId/labels')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), APP_LIMIT_ARRAY_LABELS_SIZE), 'Array of user labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_LABELS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.')
->inject('response')
->inject('dbForProject')
@@ -1211,7 +1220,7 @@ Http::put('/v1/users/:userId/labels')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/verification/phone')
App::patch('/v1/users/:userId/verification/phone')
->desc('Update phone verification')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.verification')
@@ -1231,7 +1240,7 @@ Http::patch('/v1/users/:userId/verification/phone')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('phoneVerification', false, new Boolean(), 'User phone verification status.')
->inject('response')
->inject('dbForProject')
@@ -1252,7 +1261,7 @@ Http::patch('/v1/users/:userId/verification/phone')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/name')
App::patch('/v1/users/:userId/name')
->desc('Update name')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.name')
@@ -1273,7 +1282,7 @@ Http::patch('/v1/users/:userId/name')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('name', '', new Text(128, 0), 'User name. Max length: 128 chars.')
->inject('response')
->inject('dbForProject')
@@ -1295,7 +1304,7 @@ Http::patch('/v1/users/:userId/name')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/password')
App::patch('/v1/users/:userId/password')
->desc('Update password')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.password')
@@ -1316,7 +1325,7 @@ Http::patch('/v1/users/:userId/password')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, enabled: $project->getAttribute('auths', [])['passwordDictionary'] ?? false, allowEmpty: true), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
->inject('response')
->inject('project')
@@ -1394,7 +1403,7 @@ Http::patch('/v1/users/:userId/password')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/email')
App::patch('/v1/users/:userId/email')
->desc('Update email')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.email')
@@ -1415,7 +1424,7 @@ Http::patch('/v1/users/:userId/email')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('email', '', new EmailValidator(allowEmpty: true), 'User email.')
->inject('response')
->inject('dbForProject')
@@ -1506,7 +1515,7 @@ Http::patch('/v1/users/:userId/email')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/phone')
App::patch('/v1/users/:userId/phone')
->desc('Update phone')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.phone')
@@ -1526,7 +1535,7 @@ Http::patch('/v1/users/:userId/phone')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('number', '', new Phone(allowEmpty: true), 'User phone number.')
->inject('response')
->inject('dbForProject')
@@ -1595,7 +1604,7 @@ Http::patch('/v1/users/:userId/phone')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/verification')
App::patch('/v1/users/:userId/verification')
->desc('Update email verification')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.verification')
@@ -1616,7 +1625,7 @@ Http::patch('/v1/users/:userId/verification')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('emailVerification', false, new Boolean(), 'User email verification status.')
->inject('response')
->inject('dbForProject')
@@ -1636,7 +1645,7 @@ Http::patch('/v1/users/:userId/verification')
$response->dynamic($user, Response::MODEL_USER);
});
Http::patch('/v1/users/:userId/prefs')
App::patch('/v1/users/:userId/prefs')
->desc('Update user preferences')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.prefs')
@@ -1654,7 +1663,7 @@ Http::patch('/v1/users/:userId/prefs')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('dbForProject')
@@ -1675,7 +1684,7 @@ Http::patch('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
Http::patch('/v1/users/:userId/targets/:targetId')
App::patch('/v1/users/:userId/targets/:targetId')
->desc('Update user target')
->groups(['api', 'users'])
->label('audits.event', 'target.update')
@@ -1695,10 +1704,10 @@ Http::patch('/v1/users/:userId/targets/:targetId')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true)
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true, ['dbForProject'])
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
@@ -1778,7 +1787,7 @@ Http::patch('/v1/users/:userId/targets/:targetId')
->dynamic($target, Response::MODEL_TARGET);
});
Http::patch('/v1/users/:userId/mfa')
App::patch('/v1/users/:userId/mfa')
->desc('Update MFA')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa')
@@ -1820,7 +1829,7 @@ Http::patch('/v1/users/:userId/mfa')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('mfa', null, new Boolean(), 'Enable or disable MFA.')
->inject('response')
->inject('dbForProject')
@@ -1842,7 +1851,7 @@ Http::patch('/v1/users/:userId/mfa')
$response->dynamic($user, Response::MODEL_USER);
});
Http::get('/v1/users/:userId/mfa/factors')
App::get('/v1/users/:userId/mfa/factors')
->desc('List factors')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -1880,7 +1889,7 @@ Http::get('/v1/users/:userId/mfa/factors')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@@ -1901,7 +1910,7 @@ Http::get('/v1/users/:userId/mfa/factors')
$response->dynamic($factors, Response::MODEL_MFA_FACTORS);
});
Http::get('/v1/users/:userId/mfa/recovery-codes')
App::get('/v1/users/:userId/mfa/recovery-codes')
->desc('Get MFA recovery codes')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -1939,7 +1948,7 @@ Http::get('/v1/users/:userId/mfa/recovery-codes')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
@@ -1962,7 +1971,7 @@ Http::get('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
Http::patch('/v1/users/:userId/mfa/recovery-codes')
App::patch('/v1/users/:userId/mfa/recovery-codes')
->desc('Create MFA recovery codes')
->groups(['api', 'users'])
->label('event', 'users.[userId].create.mfa.recovery-codes')
@@ -2004,7 +2013,7 @@ Http::patch('/v1/users/:userId/mfa/recovery-codes')
]
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2034,7 +2043,7 @@ Http::patch('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
Http::put('/v1/users/:userId/mfa/recovery-codes')
App::put('/v1/users/:userId/mfa/recovery-codes')
->desc('Update MFA recovery codes (regenerate)')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa.recovery-codes')
@@ -2077,7 +2086,7 @@ Http::put('/v1/users/:userId/mfa/recovery-codes')
public: false,
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2106,7 +2115,7 @@ Http::put('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
Http::delete('/v1/users/:userId/mfa/authenticators/:type')
App::delete('/v1/users/:userId/mfa/authenticators/:type')
->desc('Delete authenticator')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete.mfa')
@@ -2150,7 +2159,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
contentType: ContentType::NONE
)
])
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
->inject('response')
->inject('dbForProject')
@@ -2176,7 +2185,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
$response->noContent();
});
Http::post('/v1/users/:userId/sessions')
App::post('/v1/users/:userId/sessions')
->desc('Create session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -2197,7 +2206,7 @@ Http::post('/v1/users/:userId/sessions')
)
]
))
->param('userId', '', new CustomId(), 'User 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('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -2269,7 +2278,7 @@ Http::post('/v1/users/:userId/sessions')
->dynamic($session, Response::MODEL_SESSION);
});
Http::post('/v1/users/:userId/tokens')
App::post('/v1/users/:userId/tokens')
->desc('Create token')
->groups(['api', 'users'])
->label('event', 'users.[userId].tokens.[tokenId].create')
@@ -2289,7 +2298,7 @@ Http::post('/v1/users/:userId/tokens')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true)
->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->inject('request')
@@ -2334,7 +2343,7 @@ Http::post('/v1/users/:userId/tokens')
->dynamic($token, Response::MODEL_TOKEN);
});
Http::delete('/v1/users/:userId/sessions/:sessionId')
App::delete('/v1/users/:userId/sessions/:sessionId')
->desc('Delete user session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].delete')
@@ -2355,8 +2364,8 @@ Http::delete('/v1/users/:userId/sessions/:sessionId')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', '', new UID(), 'Session ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('sessionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Session ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2385,7 +2394,7 @@ Http::delete('/v1/users/:userId/sessions/:sessionId')
$response->noContent();
});
Http::delete('/v1/users/:userId/sessions')
App::delete('/v1/users/:userId/sessions')
->desc('Delete user sessions')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.delete')
@@ -2406,7 +2415,7 @@ Http::delete('/v1/users/:userId/sessions')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2435,7 +2444,7 @@ Http::delete('/v1/users/:userId/sessions')
$response->noContent();
});
Http::delete('/v1/users/:userId')
App::delete('/v1/users/:userId')
->desc('Delete user')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete')
@@ -2456,7 +2465,7 @@ Http::delete('/v1/users/:userId')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2487,7 +2496,7 @@ Http::delete('/v1/users/:userId')
$response->noContent();
});
Http::delete('/v1/users/:userId/targets/:targetId')
App::delete('/v1/users/:userId/targets/:targetId')
->desc('Delete user target')
->groups(['api', 'users'])
->label('audits.event', 'target.delete')
@@ -2508,8 +2517,8 @@ Http::delete('/v1/users/:userId/targets/:targetId')
],
contentType: ContentType::NONE
))
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('targetId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID.', false, ['dbForProject'])
->inject('queueForEvents')
->inject('queueForDeletes')
->inject('response')
@@ -2545,7 +2554,7 @@ Http::delete('/v1/users/:userId/targets/:targetId')
$response->noContent();
});
Http::delete('/v1/users/identities/:identityId')
App::delete('/v1/users/identities/:identityId')
->desc('Delete identity')
->groups(['api', 'users'])
->label('event', 'users.[userId].identities.[identityId].delete')
@@ -2566,7 +2575,7 @@ Http::delete('/v1/users/identities/:identityId')
],
contentType: ContentType::NONE,
))
->param('identityId', '', new UID(), 'Identity ID.')
->param('identityId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Identity ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2588,7 +2597,7 @@ Http::delete('/v1/users/identities/:identityId')
return $response->noContent();
});
Http::post('/v1/users/:userId/jwts')
App::post('/v1/users/:userId/jwts')
->desc('Create user JWT')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -2605,8 +2614,8 @@ Http::post('/v1/users/:userId/jwts')
)
]
))
->param('userId', '', new UID(), 'User ID.')
->param('sessionId', '', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('sessionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true, ['dbForProject'])
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForProject')
@@ -2645,7 +2654,7 @@ Http::post('/v1/users/:userId/jwts')
])]), Response::MODEL_JWT);
});
Http::get('/v1/users/usage')
App::get('/v1/users/usage')
->desc('Get users usage stats')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -2665,8 +2674,8 @@ Http::get('/v1/users/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->inject('authorization')
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
->inject('register')
->action(function (string $range, Response $response, Database $dbForProject) {
$periods = Config::getParam('usage', []);
$stats = $usage = [];
@@ -2676,7 +2685,7 @@ Http::get('/v1/users/usage')
METRIC_SESSIONS,
];
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $count => $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
File diff suppressed because it is too large Load Diff
+137 -269
View File
@@ -6,9 +6,8 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Key;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Event\Execution;
use Appwrite\Event\Func;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Network\Cors;
@@ -34,6 +33,7 @@ use Appwrite\Utopia\View;
use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Http\Request as SwooleRequest;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -45,7 +45,6 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Domains\Domain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
@@ -60,7 +59,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey)
{
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
@@ -68,16 +67,16 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
// TODO: (@Meldiron) Remove after 1.7.x migration
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
} else {
$rule = $authorization->skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$host]),
Query::limit(1)
])
)[0] ?? new Document();
}
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$rule = Authorization::skip(function () use ($dbForPlatform, $host, $isMd5) {
if ($isMd5) {
return $dbForPlatform->getDocument('rules', md5($host));
}
return $dbForPlatform->findOne('rules', [
Query::equal('domain', [$host]),
]) ?? new Document();
});
$errorView = __DIR__ . '/../views/general/error.phtml';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
@@ -85,32 +84,22 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$platformHostnames = $platform['hostnames'] ?? [];
if ($rule->isEmpty()) {
$denyDomains = [];
$denyEnvVars = [
System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''),
System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
System::getEnv('_APP_DOMAIN_SITES', ''),
];
foreach ($denyEnvVars as $denyEnvVar) {
foreach (\explode(',', $denyEnvVar) as $denyDomain) {
if (empty($denyDomain)) {
continue;
}
$denyDomains[] = $denyDomain;
}
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$appDomainFunctions = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$appDomainSites = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($appDomainFunctionsFallback) && \str_ends_with($host, $appDomainFunctionsFallback)) {
$appDomainFunctions = $appDomainFunctionsFallback;
}
foreach ($denyDomains as $denyDomain) {
if ($host === $denyDomain) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.', view: $errorView);
}
if ($host === $appDomainFunctions || $host === $appDomainSites) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.', view: $errorView);
}
if (\str_ends_with($host, $denyDomain)) {
$exception = new AppwriteException(AppwriteException::RULE_NOT_FOUND, 'This domain is not connected to any Appwrite resources. Visit domains tab under function/site settings to configure it.', view: $errorView);
if (\str_ends_with($host, $appDomainFunctions) || \str_ends_with($host, $appDomainSites)) {
$exception = new AppwriteException(AppwriteException::RULE_NOT_FOUND, 'This domain is not connected to any Appwrite resources. Visit domains tab under function/site settings to configure it.', view: $errorView);
$exception->addCTA('Start with this domain', $url . '/console');
throw $exception;
}
$exception->addCTA('Start with this domain', $url . '/console');
throw $exception;
}
if (!in_array($host, $platformHostnames)) {
@@ -122,7 +111,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
$projectId = $rule->getAttribute('projectId');
$project = $authorization->skip(
$project = Authorization::skip(
fn () => $dbForPlatform->getDocument('projects', $projectId)
);
@@ -130,7 +119,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
}
/**
@@ -169,7 +158,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
/** @var Document $deployment */
if (!empty($rule->getAttribute('deploymentId', ''))) {
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
} else {
// 1.6.x DB schema compatibility
// TODO: Make sure deploymentId is never empty, and remove this code
@@ -183,15 +172,15 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
// Document of site or function
$resource = $resourceType === 'function' ?
$authorization->skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
$authorization->skip(fn () => $dbForProject->getDocument('sites', $resourceId));
Authorization::skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $resourceId));
// ID of active deployments
// Attempts to use attribute from both schemas (1.6 and 1.7)
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
// Get deployment document, as intended originally
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
}
if ($deployment->getAttribute('resourceType', '') === 'functions') {
@@ -210,8 +199,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
$resource = $type === 'function' ?
$authorization->skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
$authorization->skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
$isPreview = $type === 'function' ? false : ($rule->getAttribute('trigger', '') !== 'manual');
@@ -253,7 +242,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$userExists = false;
$userId = $payload['userId'] ?? '';
if (!empty($userId)) {
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
if (!$user->isEmpty() && $user->getAttribute('status', false)) {
$userExists = true;
}
@@ -266,7 +255,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
$membershipExists = false;
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
if (!$project->isEmpty() && isset($user)) {
$teamId = $project->getAttribute('teamId', '');
$membership = $user->find('teamId', $teamId, 'memberships');
@@ -696,11 +685,14 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
throw $th;
}
} finally {
if ($type === 'function' || $type === 'site') {
$queueForExecutions
if ($type === 'function') {
$queueForFunctions
->setType(Func::TYPE_ASYNC_WRITE)
->setExecution($execution)
->setProject($project)
->trigger();
} elseif ($type === 'site') { // TODO: Move it to logs worker later
$dbForProject->createDocument('executions', $execution);
}
}
@@ -810,20 +802,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
->setProject($project)
->trigger();
/* cleanup */
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
$resourceType = $type === 'function'
? RESOURCE_TYPE_FUNCTIONS
: RESOURCE_TYPE_SITES;
$queueForDeletes
->setProject($project)
->setResourceType($resourceType)
->setResource($resource->getSequence())
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
->trigger();
}
return true;
} elseif ($type === 'api') {
return false;
@@ -834,9 +812,11 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
} else {
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type, view: $errorView);
}
return false;
}
Http::init()
App::init()
->groups(['api'])
->inject('project')
->inject('mode')
@@ -846,7 +826,7 @@ Http::init()
}
});
Http::init()
App::init()
->groups(['database', 'functions', 'sites', 'messaging'])
->inject('project')
->inject('request')
@@ -859,7 +839,7 @@ Http::init()
}
});
Http::init()
App::init()
->groups(['api', 'web'])
->inject('utopia')
->inject('swooleRequest')
@@ -874,7 +854,7 @@ Http::init()
->inject('geodb')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForExecutions')
->inject('queueForFunctions')
->inject('executor')
->inject('platform')
->inject('isResourceBlocked')
@@ -882,18 +862,15 @@ Http::init()
->inject('devKey')
->inject('apiKey')
->inject('cors')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Execution $queueForExecutions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors) {
/*
* Appwrite Router
*/
$hostname = $request->getHostname() ?? '';
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (!in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -947,15 +924,7 @@ Http::init()
$endDomain->getRegisterable() !== ''
);
$localHosts = ['localhost','localhost:'.$request->getPort()];
$migrationHost = System::getEnv('_APP_MIGRATION_HOST');
if (!empty($migrationHost)) {
$localHosts[] = $migrationHost;
$localHosts[] = $migrationHost.':'.$request->getPort();
}
$isLocalHost = in_array($request->getHostname(), $localHosts);
$isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort();
$isIpAddress = filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false;
$isConsoleProject = $project->getAttribute('$id', '') === 'console';
@@ -1002,7 +971,7 @@ Http::init()
}
if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https' && !in_array(($swooleRequest->header['host'] ?? ''), $localHosts)) { // localhost allowed for proxy
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost') { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
@@ -1017,7 +986,7 @@ Http::init()
*
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
Http::init()
App::init()
->groups(['api', 'web'])
->inject('request')
->inject('response')
@@ -1057,15 +1026,14 @@ Http::init()
/**
* Automatic certificate generation
*/
Http::init()
App::init()
->groups(['api', 'web'])
->inject('request')
->inject('console')
->inject('dbForPlatform')
->inject('queueForCertificates')
->inject('platform')
->inject('authorization')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) {
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform) {
$hostname = $request->getHostname();
$cache = Config::getParam('hostnames', []);
$platformHostnames = $platform['hostnames'] ?? [];
@@ -1093,76 +1061,67 @@ Http::init()
}
// 4. Check/create rule (requires DB access)
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, &$cache) {
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$document = $isMd5
? $dbForPlatform->getDocument('rules', md5($domain->get()))
: $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain->get()]),
]);
if (!$document->isEmpty()) {
return;
}
// 5. Create new rule
$owner = '';
// Mark owner as Appwrite if its appwrite-owned domain
$appwriteDomains = [];
$appwriteDomainEnvs = [
System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''),
System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
System::getEnv('_APP_DOMAIN_SITES', ''),
];
foreach ($appwriteDomainEnvs as $appwriteDomainEnv) {
foreach (\explode(',', $appwriteDomainEnv) as $appwriteDomain) {
if (empty($appwriteDomain)) {
continue;
}
$appwriteDomains[] = $appwriteDomain;
}
}
foreach ($appwriteDomains as $appwriteDomain) {
if (\str_ends_with($domain->get(), $appwriteDomain)) {
$owner = 'Appwrite';
break;
}
}
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
$document = new Document([
'$id' => $ruleId,
'domain' => $domain->get(),
'type' => 'api',
'status' => 'verifying',
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'search' => implode(' ', [$ruleId, $domain->get()]),
'owner' => $owner,
'region' => $console->getAttribute('region')
Authorization::disable();
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$document = $isMd5
? $dbForPlatform->getDocument('rules', md5($domain->get()))
: $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain->get()]),
]);
$dbForPlatform->createDocument('rules', $document);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$queueForCertificates
->setDomain($document)
->setSkipRenewCheck(true)
->trigger();
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
if (!$document->isEmpty()) {
return;
}
});
// 5. Create new rule
$owner = '';
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
$funcDomain = $fallback;
}
if (
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
) {
$owner = 'Appwrite';
}
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
$document = new Document([
'$id' => $ruleId,
'domain' => $domain->get(),
'type' => 'api',
'status' => 'verifying',
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'search' => implode(' ', [$ruleId, $domain->get()]),
'owner' => $owner,
'region' => $console->getAttribute('region')
]);
$dbForPlatform->createDocument('rules', $document);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$queueForCertificates
->setDomain($document)
->setSkipRenewCheck(true)
->trigger();
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
Authorization::reset();
}
});
Http::options()
App::options()
->inject('utopia')
->inject('swooleRequest')
->inject('request')
@@ -1172,7 +1131,7 @@ Http::options()
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForExecutions')
->inject('queueForFunctions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
@@ -1182,17 +1141,14 @@ Http::options()
->inject('devKey')
->inject('apiKey')
->inject('cors')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors) {
/*
* Appwrite Router
*/
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1216,7 +1172,7 @@ Http::options()
->trigger();
});
Http::error()
App::error()
->inject('error')
->inject('utopia')
->inject('request')
@@ -1226,8 +1182,7 @@ Http::error()
->inject('log')
->inject('queueForStatsUsage')
->inject('devKey')
->inject('authorization')
->action(function (Throwable $error, Http $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute();
$class = \get_class($error);
@@ -1252,7 +1207,7 @@ Http::error()
}
switch ($class) {
case Utopia\Exception::class:
case 'Utopia\Exception':
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
switch ($code) {
case 400:
@@ -1263,10 +1218,10 @@ Http::error()
break;
}
break;
case Utopia\Database\Exception\Authorization::class:
case 'Utopia\Database\Exception\Authorization':
$error = new AppwriteException(AppwriteException::USER_UNAUTHORIZED);
break;
case Utopia\Database\Exception\Timeout::class:
case 'Utopia\Database\Exception\Timeout':
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
break;
}
@@ -1309,7 +1264,7 @@ Http::error()
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
*/
if (!$publish && $project->getId() !== 'console') {
if (!DBUser::isPrivileged($authorization->getRoles())) {
if (!DBUser::isPrivileged(Authorization::getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
@@ -1371,87 +1326,7 @@ Http::error()
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', $authorization->getRoles());
try {
/* add queries to log */
$queries = $request->getParam('queries', []);
if (!empty($queries) && is_array($queries)) {
$parsedQueries = Query::parseQueries($queries);
// format query by removing sensitive values
$formatQuery = function (array $queryArray) use (&$formatQuery): ?array {
$method = $queryArray['method'] ?? '';
$values = $queryArray['values'] ?? [];
$attribute = $queryArray['attribute'] ?? '';
if (!is_string($method) || $method === '') {
return null;
}
// logical queries - recursively format nested queries
if (in_array($method, [Query::TYPE_AND, Query::TYPE_OR], true)) {
$nested = [];
foreach ($values as $nestedArray) {
if (is_array($nestedArray)) {
$formatted = $formatQuery($nestedArray);
if ($formatted !== null) {
$nested[] = $formatted;
}
}
}
return empty($nested) ? null : [$method => $nested];
}
// select - show selected attributes
if ($method === Query::TYPE_SELECT) {
$attributes = array_values(array_filter($values, 'is_string'));
return [$method => $attributes];
}
// pagination
if (in_array($method, [
Query::TYPE_LIMIT,
Query::TYPE_OFFSET,
Query::TYPE_CURSOR_AFTER,
Query::TYPE_CURSOR_BEFORE
], true)) {
return [$method => []];
}
// orders
if (in_array($method, [
Query::TYPE_ORDER_DESC,
Query::TYPE_ORDER_ASC,
Query::TYPE_ORDER_RANDOM
], true)) {
return [$method => !empty($attribute) ? [$attribute] : []];
}
// filter
if (!empty($attribute)) {
return [$method => [$attribute]];
}
// fallback
return [$method => []];
};
$formattedQueries = [];
foreach ($parsedQueries as $query) {
$formatted = $formatQuery($query->toArray());
if ($formatted !== null) {
$formattedQueries[] = $formatted;
}
}
if (!empty($formattedQueries)) {
$log->addExtra('queries', $formattedQueries);
}
}
} catch (Throwable $_) {
// don't fail the error handler
}
$log->addExtra('roles', Authorization::getRoles());
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
if (!empty($sdk)) {
@@ -1503,7 +1378,7 @@ Http::error()
$type = $error->getType();
$output = Http::isDevelopment() ? [
$output = App::isDevelopment() ? [
'message' => $message,
'code' => $code,
'file' => $file,
@@ -1527,7 +1402,7 @@ Http::error()
$template = $error->getView() ?? (($route) ? $route->getLabel('error', null) : null);
// TODO: Ideally use group 'api' here, but all wildcard routes seem to have 'api' at the moment
if (empty($route) || !\str_starts_with($route->getPath(), '/v1')) {
if (!\str_starts_with($route->getPath(), '/v1')) {
$template = __DIR__ . '/../views/general/error.phtml';
}
@@ -1536,7 +1411,7 @@ Http::error()
$layout
->setParam('title', $project->getAttribute('name') . ' - Error')
->setParam('development', Http::isDevelopment())
->setParam('development', App::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $output['message'] ?? '')
@@ -1555,7 +1430,7 @@ Http::error()
);
});
Http::get('/robots.txt')
App::get('/robots.txt')
->desc('Robots.txt File')
->label('scope', 'public')
->label('docs', false)
@@ -1568,29 +1443,26 @@ Http::get('/robots.txt')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForExecutions')
->inject('queueForFunctions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
->inject('platform')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
});
Http::get('/humans.txt')
App::get('/humans.txt')
->desc('Humans.txt File')
->label('scope', 'public')
->label('docs', false)
@@ -1603,29 +1475,26 @@ Http::get('/humans.txt')
->inject('getProjectDB')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForExecutions')
->inject('queueForFunctions')
->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
->inject('platform')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
});
Http::get('/.well-known/acme-challenge/*')
App::get('/.well-known/acme-challenge/*')
->desc('SSL Verification')
->label('scope', 'public')
->label('docs', false)
@@ -1678,7 +1547,7 @@ Http::get('/.well-known/acme-challenge/*')
include_once __DIR__ . '/shared/api.php';
include_once __DIR__ . '/shared/api/auth.php';
Http::get('/v1/ping')
App::get('/v1/ping')
->groups(['api', 'general'])
->desc('Test the connection between the Appwrite and the SDK.')
->label('scope', 'global')
@@ -1703,8 +1572,7 @@ Http::get('/v1/ping')
->inject('project')
->inject('dbForPlatform')
->inject('queueForEvents')
->inject('authorization')
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents, Authorization $authorization) {
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) {
if ($project->isEmpty() || $project->getId() === 'console') {
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
}
@@ -1716,7 +1584,7 @@ Http::get('/v1/ping')
->setAttribute('pingCount', $pingCount)
->setAttribute('pingedAt', $pingedAt);
$authorization->skip(function () use ($dbForPlatform, $project) {
Authorization::skip(function () use ($dbForPlatform, $project) {
$dbForPlatform->updateDocument('projects', $project->getId(), $project);
});
@@ -1728,7 +1596,7 @@ Http::get('/v1/ping')
});
// Preview authorization
Http::get('/_appwrite/authorize')
App::get('/_appwrite/authorize')
->inject('request')
->inject('response')
->inject('previewHostname')
@@ -1755,7 +1623,7 @@ Http::get('/_appwrite/authorize')
->redirect($protocol . '://' . $host . $path);
});
Http::wildcard()
App::wildcard()
->groups(['api'])
->label('scope', 'global')
->action(function () {
+15 -17
View File
@@ -5,6 +5,7 @@ global $utopia, $request, $response;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -12,14 +13,13 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Http;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
Http::get('/v1/mock/tests/general/oauth2')
App::get('/v1/mock/tests/general/oauth2')
->desc('OAuth Login')
->groups(['mock'])
->label('scope', 'public')
@@ -31,11 +31,10 @@ Http::get('/v1/mock/tests/general/oauth2')
->param('state', '', new Text(1024), 'OAuth2 state.')
->inject('response')
->action(function (string $client_id, string $redirectURI, string $scope, string $state, Response $response) {
$response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
});
Http::get('/v1/mock/tests/locale')
App::get('/v1/mock/tests/locale')
->desc('Mock locale translation key')
->groups(['mock'])
->label('scope', 'public')
@@ -54,7 +53,7 @@ Http::get('/v1/mock/tests/locale')
$response->send($locale->getText('mock'));
});
Http::get('/v1/mock/tests/general/oauth2/token')
App::get('/v1/mock/tests/general/oauth2/token')
->desc('OAuth2 Token')
->groups(['mock'])
->label('scope', 'public')
@@ -100,7 +99,7 @@ Http::get('/v1/mock/tests/general/oauth2/token')
}
});
Http::get('/v1/mock/tests/general/oauth2/user')
App::get('/v1/mock/tests/general/oauth2/user')
->desc('OAuth2 User')
->groups(['mock'])
->label('scope', 'public')
@@ -121,7 +120,7 @@ Http::get('/v1/mock/tests/general/oauth2/user')
]);
});
Http::get('/v1/mock/tests/general/oauth2/user-unverified')
App::get('/v1/mock/tests/general/oauth2/user-unverified')
->desc('OAuth2 User Unverified')
->groups(['mock'])
->label('scope', 'public')
@@ -142,7 +141,7 @@ Http::get('/v1/mock/tests/general/oauth2/user-unverified')
]);
});
Http::get('/v1/mock/tests/general/oauth2/success')
App::get('/v1/mock/tests/general/oauth2/success')
->desc('OAuth2 Success')
->groups(['mock'])
->label('scope', 'public')
@@ -155,7 +154,7 @@ Http::get('/v1/mock/tests/general/oauth2/success')
]);
});
Http::get('/v1/mock/tests/general/oauth2/failure')
App::get('/v1/mock/tests/general/oauth2/failure')
->desc('OAuth2 Failure')
->groups(['mock'])
->label('scope', 'public')
@@ -170,7 +169,7 @@ Http::get('/v1/mock/tests/general/oauth2/failure')
]);
});
Http::post('/v1/mock/api-key-unprefixed')
App::post('/v1/mock/api-key-unprefixed')
->desc('Create API Key (without standard prefix)')
->groups(['mock', 'api', 'projects'])
->label('scope', 'public')
@@ -191,7 +190,7 @@ Http::post('/v1/mock/api-key-unprefixed')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$scopes = array_keys(Config::getParam('projectScopes'));
$scopes = array_keys(Config::getParam('scopes'));
$key = new Document([
'$id' => ID::unique(),
@@ -200,9 +199,8 @@ Http::post('/v1/mock/api-key-unprefixed')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'name' => 'Outdated key',
'scopes' => $scopes,
'expire' => null,
@@ -220,7 +218,7 @@ Http::post('/v1/mock/api-key-unprefixed')
->dynamic($key, Response::MODEL_KEY);
});
Http::get('/v1/mock/github/callback')
App::get('/v1/mock/github/callback')
->desc('Create installation document using GitHub installation id')
->groups(['mock', 'api', 'vcs'])
->label('scope', 'public')
@@ -280,12 +278,12 @@ Http::get('/v1/mock/github/callback')
]);
});
Http::shutdown()
App::shutdown()
->groups(['mock'])
->inject('utopia')
->inject('response')
->inject('request')
->action(function (Http $utopia, Response $response, Request $request) {
->action(function (App $utopia, Response $response, Request $request) {
$result = [];
$route = $utopia->getRoute();
+265 -143
View File
@@ -10,17 +10,18 @@ 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\Extend\Exception as AppwriteException;
use Appwrite\Functions\EventProcessor;
use Appwrite\SDK\Method;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
@@ -29,8 +30,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Http;
use Utopia\Queue\Publisher;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Validator\WhiteList;
@@ -74,7 +74,172 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
return $label;
};
Http::init()
/**
* Get database type prefixed metric
* Prefixes the metric with database type if it's not legacy or tablesdb
*
* @param string $databaseType Database type (e.g., 'documentsdb', 'tablesdb', 'legacy', '')
* @param string $metric Base metric constant (e.g., METRIC_DATABASES, METRIC_COLLECTIONS)
* @return string Prefixed metric or original metric if no prefix needed
*/
$getDatabaseTypePrefixedMetric = function (string $databaseType, string $metric): string {
return implode('.', array_filter([$databaseType, $metric]));
};
/**
* This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**.
*
* Accounts can be created in many ways beyond `createAccount`
* (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.
*/
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
// Only trigger events for user creation with the database listener.
if ($document->getCollection() !== 'users') {
return;
}
$queueForEvents
->setEvent('users.[userId].create')
->setParam('userId', $document->getId())
->setPayload($response->output($document, Response::MODEL_USER));
// Trigger functions, webhooks, and realtime events
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger webhooks events only if a project has them enabled */
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
/** Trigger realtime events only for non console events */
if ($queueForEvents->getProject()->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
};
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage, string $databaseType) use ($getDatabaseTypePrefixedMetric) {
$value = 1;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
}
switch (true) {
case $document->getCollection() === 'teams':
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
break;
case $document->getCollection() === 'users':
$queueForStatsUsage->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case $document->getCollection() === 'sessions': // sessions
$queueForStatsUsage->addMetric(METRIC_SESSIONS, $value); //per project
break;
case $document->getCollection() === 'databases': // databases
$metric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASES);
$queueForStatsUsage->addMetric($metric, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_COLLECTIONS);
$databaseIdCollectionMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_COLLECTIONS);
$queueForStatsUsage
->addMetric($collectionMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdCollectionMetric), $value);
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
// for databases route documents are getting created via getDatabasesDB and stats are maintained in the resources
// but keeping it since other routes might created documents still directly via dbForProject
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$documentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DOCUMENTS);
$databaseIdDocumentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_DOCUMENTS);
$databaseIdCollectionIdDocumentsMetric = $getDatabaseTypePrefixedMetric($databaseType, METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS);
$queueForStatsUsage
->addMetric($documentsMetric, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, $databaseIdDocumentsMetric), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], $databaseIdCollectionIdDocumentsMetric), $value); // per collection
break;
case $document->getCollection() === 'buckets': //buckets
$queueForStatsUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$queueForStatsUsage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$queueForStatsUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'sites':
$queueForStatsUsage
->addMetric(METRIC_SITES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$queueForStatsUsage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
break;
default:
break;
}
};
App::init()
->groups(['api'])
->inject('utopia')
->inject('request')
@@ -88,8 +253,7 @@ Http::init()
->inject('mode')
->inject('team')
->inject('apiKey')
->inject('authorization')
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
$route = $utopia->getRoute();
/**
@@ -157,6 +321,10 @@ Http::init()
// Step 5: API Key Authentication
if (!empty($apiKey)) {
// Verify no user session exists simultaneously
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if key is expired
if ($apiKey->isExpired()) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
@@ -170,7 +338,7 @@ Http::init()
// Handle special app role case
if ($apiKey->getRole() === User::ROLE_APPS) {
// Disable authorization checks for API keys
$authorization->setDefaultStatus(false);
Authorization::setDefaultStatus(false);
$user = new User([
'$id' => '',
@@ -185,38 +353,23 @@ Http::init()
}
// For standard keys, update last accessed time
if (\in_array($apiKey->getType(), [API_KEY_STANDARD, API_KEY_ORGANIZATION, API_KEY_ACCOUNT])) {
$dbKey = null;
if (!empty($apiKey->getProjectId())) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getUserId())) {
$dbKey = $user->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getTeamId())) {
$dbKey = $team->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
}
if ($apiKey->getType() === API_KEY_STANDARD) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
if (!$dbKey) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$updates = new Document();
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$updates->setAttribute('accessedAt', DateTime::now());
$dbKey->setAttribute('accessedAt', DateTime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
$sdkValidator = new WhiteList($servers, true);
@@ -227,21 +380,12 @@ Http::init()
if (!in_array($sdk, $sdks)) {
$sdks[] = $sdk;
$dbKey->setAttribute('sdks', $sdks);
$updates->setAttribute('sdks', $sdks);
$updates->setAttribute('accessedAt', Datetime::now());
}
}
if (!$updates->isEmpty()) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('keys', $dbKey->getId(), $updates));
if (!empty($apiKey->getProjectId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
} elseif (!empty($apiKey->getUserId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('users', $user->getId()));
} elseif (!empty($apiKey->getTeamId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId()));
/** Update access time as well */
$dbKey->setAttribute('accessedAt', Datetime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
}
@@ -268,14 +412,14 @@ Http::init()
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
}
$authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
}
$scopes = \array_unique($scopes);
$authorization->addRole($role);
foreach ($user->getRoles($authorization) as $authRole) {
$authorization->addRole($authRole);
Authorization::setRole($role);
foreach ($user->getRoles() as $authRole) {
Authorization::setRole($authRole);
}
// Step 6: Update project and user last activity
@@ -283,7 +427,7 @@ Http::init()
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
}
}
@@ -318,7 +462,7 @@ Http::init()
if (
array_key_exists($namespace, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$namespace]
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
}
@@ -356,13 +500,16 @@ Http::init()
}
});
Http::init()
App::init()
->groups(['api'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('user')
->inject('publisher')
->inject('publisherFunctions')
->inject('publisherWebhooks')
->inject('queueForEvents')
->inject('queueForMessaging')
->inject('queueForAudits')
@@ -372,6 +519,7 @@ Http::init()
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForMails')
->inject('queueForMigrations')
->inject('dbForProject')
->inject('timelimit')
->inject('resourceToken')
@@ -381,15 +529,19 @@ Http::init()
->inject('devKey')
->inject('telemetry')
->inject('platform')
->inject('authorization')
->action(function (Http $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Migration $queueForMigrations, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform) use ($usageDatabaseListener, $eventDatabaseListener) {
$route = $utopia->getRoute();
$path = $route->getMatchedPath();
$databaseType = match (true) {
str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB,
default => '',
};
if (
array_key_exists('rest', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['rest']
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -397,6 +549,7 @@ Http::init()
/*
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
@@ -419,7 +572,7 @@ Http::init()
$closestLimit = null;
$roles = $authorization->getRoles();
$roles = Authorization::getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
@@ -432,6 +585,7 @@ Http::init()
$abuse = new Abuse($timeLimit);
$remaining = $timeLimit->remaining();
$limit = $timeLimit->limit();
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
@@ -502,16 +656,40 @@ Http::init()
$queueForBuilds->setPlatform($platform);
$queueForMails->setPlatform($platform);
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.
$queueForEventsClone = new Event($publisher);
$queueForFunctions = new Func($publisherFunctions);
$queueForWebhooks = new Webhook($publisherWebhooks);
$queueForRealtime = new Realtime();
// to get consumed in the stats worker without calling to database
$queueForStatsUsage->setContext('database', new Document(['type' => $databaseType]));
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage, $databaseType))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage, $databaseType))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage, $databaseType))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage, $databaseType))
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage, $databaseType))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
$response,
$queueForEventsClone->from($queueForEvents),
$queueForFunctions->from($queueForEvents),
$queueForWebhooks->from($queueForEvents),
$queueForRealtime->from($queueForEvents)
));
$useCache = $route->getLabel('cache', false);
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
if ($useCache) {
$route = $utopia->match($request);
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged($authorization->getRoles());
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged(Authorization::getRoles());
$key = $request->cacheIdentifier();
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
@@ -528,10 +706,10 @@ Http::init()
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
$bucketId = $parts[1] ?? null;
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -542,7 +720,8 @@ Http::init()
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -553,7 +732,7 @@ Http::init()
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
@@ -564,11 +743,11 @@ Http::init()
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
//Do not update transformedAt if it's a console user
if (!User::isPrivileged($authorization->getRoles())) {
if (!User::isPrivileged(Authorization::getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
}
}
}
@@ -592,7 +771,7 @@ Http::init()
}
});
Http::init()
App::init()
->groups(['session'])
->inject('user')
->inject('request')
@@ -612,14 +791,14 @@ Http::init()
* Delete older sessions if the number of sessions have crossed
* the session limit set for the project
*/
Http::shutdown()
App::shutdown()
->groups(['session'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (Http $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
$session = $response->getPayload();
$userId = $session['userId'] ?? '';
@@ -646,7 +825,7 @@ Http::shutdown()
$dbForProject->purgeCachedDocument('users', $userId);
});
Http::shutdown()
App::shutdown()
->groups(['api'])
->inject('utopia')
->inject('request')
@@ -664,10 +843,7 @@ Http::shutdown()
->inject('queueForWebhooks')
->inject('queueForRealtime')
->inject('dbForProject')
->inject('authorization')
->inject('timelimit')
->inject('eventProcessor')
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor) use ($parseLabel) {
->action(function (App $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -676,15 +852,9 @@ Http::shutdown()
$queueForEvents->setPayload($responsePayload);
}
// Get project and function/webhook events (cached)
$functionsEvents = $eventProcessor->getFunctionsEvents($project, $dbForProject);
$webhooksEvents = $eventProcessor->getWebhooksEvents($project);
// Generate events for this operation
$generatedEvents = Event::generateEvents(
$queueForEvents->getEvent(),
$queueForEvents->getParams()
);
$queueForFunctions
->from($queueForEvents)
->trigger();
if ($project->getId() !== 'console') {
$queueForRealtime
@@ -692,69 +862,21 @@ Http::shutdown()
->trigger();
}
// Only trigger functions if there are matching function events
if (!empty($functionsEvents)) {
foreach ($generatedEvents as $event) {
if (isset($functionsEvents[$event])) {
$queueForFunctions
->from($queueForEvents)
->trigger();
break;
}
}
}
// Only trigger webhooks if there are matching webhook events
if (!empty($webhooksEvents)) {
foreach ($generatedEvents as $event) {
if (isset($webhooksEvents[$event])) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
break;
}
}
/** Trigger webhooks events only if a project has them enabled
* A future optimisation is to only trigger webhooks if the webhook is "enabled"
* But it might have performance implications on the API due to the number of webhooks etc.
* Some profiling is needed to see if this is a problem.
*/
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
}
$route = $utopia->getRoute();
$requestParams = $route->getParamsValues();
/**
* Abuse labels
*/
$abuseEnabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
$abuseResetCode = $route->getLabel('abuse-reset', []);
$abuseResetCode = \is_array($abuseResetCode) ? $abuseResetCode : [$abuseResetCode];
if ($abuseEnabled && \count($abuseResetCode) > 0 && \in_array($response->getStatusCode(), $abuseResetCode)) {
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($abuseKeyLabel as $abuseKey) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
$timeLimit
->setParam('{projectId}', $project->getId())
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath())
->setParam('{method}', $request->getMethod())
->setParam('{chunkId}', (int)($start / ($end + 1 - $start)));
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (!empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
$abuse->reset();
}
}
/**
* Audit labels
*/
@@ -847,11 +969,11 @@ Http::shutdown()
$key = $request->cacheIdentifier();
$signature = md5($data['payload']);
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', 0);
$now = DateTime::now();
if ($cacheLog->isEmpty()) {
$authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
'$id' => $key,
'resource' => $resource,
'resourceType' => $resourceType,
@@ -861,7 +983,7 @@ Http::shutdown()
])));
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$cacheLog->setAttribute('accessedAt', $now);
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
// Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load()
$cache->save($key, $data['payload']);
}
@@ -873,7 +995,7 @@ Http::shutdown()
}
if ($project->getId() !== 'console') {
if (!User::isPrivileged($authorization->getRoles())) {
if (!User::isPrivileged(Authorization::getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
@@ -892,7 +1014,7 @@ Http::shutdown()
}
});
Http::init()
App::init()
->groups(['usage'])
->action(function () {
if (System::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
+6 -7
View File
@@ -4,14 +4,14 @@ use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Http;
use Utopia\System\System;
Http::init()
App::init()
->groups(['mfaProtected'])
->inject('session')
->action(function (Document $session) {
@@ -30,14 +30,13 @@ Http::init()
}
});
Http::init()
App::init()
->groups(['auth'])
->inject('utopia')
->inject('request')
->inject('project')
->inject('geodb')
->inject('authorization')
->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist);
@@ -50,8 +49,8 @@ Http::init()
$route = $utopia->match($request);
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isAppUser = User::isApp(Authorization::getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
+43
View File
@@ -0,0 +1,43 @@
<?php
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
App::init()
->groups(['web'])
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$response
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
});
App::get('/')
->alias('auth/*')
->alias('/invite')
->alias('/login')
->alias('/mfa')
->alias('/card/*')
->alias('/recover')
->alias('/register/*')
->groups(['web'])
->label('permission', 'public')
->label('scope', 'home')
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$url = parse_url($request->getURI());
$target = "/console{$url['path']}";
$params = $request->getParams();
if (!empty($params)) {
$target .= "?" . \http_build_query($params);
}
if ($url['fragment'] ?? false) {
$target .= "#{$url['fragment']}";
}
$response->redirect($target);
});
+2 -2
View File
@@ -1,10 +1,10 @@
<?php
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Http;
Http::get('/versions')
App::get('/versions')
->desc('Get Version')
->groups(['home', 'web'])
->label('scope', 'public')
+74 -87
View File
@@ -11,8 +11,7 @@ use Swoole\Http\Server;
use Swoole\Process;
use Swoole\Table;
use Swoole\Timer;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Adapter\SQL as AuditAdapterSQL;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Compression\Compression;
@@ -26,7 +25,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Http;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Pools\Group;
@@ -100,19 +99,11 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
$risky = false;
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
$risky = true;
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
$risky = true;
} elseif ($domains->get(md5($domain), 'value') === 1) {
// executions request coming from custom domain
$risky = true;
} else {
foreach (\explode(',', System::getEnv('_APP_DOMAIN_FUNCTIONS')) as $riskyDomain) {
if (empty($riskyDomain)) {
continue;
}
if (str_ends_with($domain, $riskyDomain)) {
$risky = true;
break;
}
}
}
if ($risky) {
@@ -181,10 +172,10 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
include __DIR__ . '/controllers/general.php';
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
function createDatabase(App $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
{
$max = 10;
$sleep = 1;
$max = 15;
$sleep = 2;
$attempts = 0;
while (true) {
@@ -194,7 +185,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
/* @var $database Database */
$database = is_callable($resource) ? $resource() : $resource;
break; // exit loop on success
} catch (\Exception $e) {
} catch (\Throwable $e) {
Console::warning(" └── Database not ready. Retrying connection ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception(' └── Failed to connect to database: ' . $e->getMessage());
@@ -205,12 +196,26 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
Console::success("[Setup] - $dbName database init started...");
// Attempt to create the database
try {
Console::info(" └── Creating database: $dbName...");
$database->create();
} catch (\Exception $e) {
Console::info(" └── Skip: metadata table already exists");
$attempts = 0;
while (true) {
try {
$attempts++;
Console::info(" └── Creating database: $dbName...");
$database->create();
break; // exit loop on success
} catch (\Exception $e) {
if ($e instanceof DuplicateException) {
Console::info(" └── Skip: metadata table already exists");
break;
}
Console::warning(" └── Database create failed. Retrying ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception(' └── Failed to create database: ' . $e->getMessage());
}
\sleep($sleep);
}
}
// Process collections
@@ -254,12 +259,12 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
}
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
$app = new Http('UTC');
$app = new App('UTC');
go(function () use ($register, $app) {
$pools = $register->get('pools');
/** @var Group $pools */
Http::setResource('pools', fn () => $pools);
App::setResource('pools', fn () => $pools);
/** @var array $collections */
$collections = Config::getParam('collections', []);
@@ -268,12 +273,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
// create appwrite database, `dbForPlatform` is a direct access call.
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
$authorization = $app->getResource('authorization');
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForPlatform);
$audit = new Audit($adapter);
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections) {
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForPlatform);
$audit->setup();
}
@@ -330,9 +332,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
}
if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
Console::info(" └── Creating screenshots bucket...");
$authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
Authorization::skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
'$id' => ID::custom('screenshots'),
'$collection' => ID::custom('buckets'),
'name' => 'Screenshots',
@@ -347,7 +349,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'search' => 'buckets Screenshots',
])));
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
Console::info(" └── Creating files collection for screenshots bucket...");
$files = $collections['buckets']['files'] ?? [];
@@ -375,7 +377,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'orders' => $index['orders'],
]), $files['indexes']);
$authorization->skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
}
});
@@ -394,16 +396,29 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
->setTenant(null)
->setNamespace(System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''));
try {
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
} catch (DuplicateException) {
Console::success('[Setup] - Skip: metadata table already exists');
$max = 15;
$sleep = 2;
$attempts = 0;
while (true) {
try {
$attempts++;
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
break; // exit loop on success
} catch (DuplicateException) {
Console::success('[Setup] - Skip: metadata table already exists');
break;
} catch (\Throwable $e) {
Console::warning(" └── Project database create failed. Retrying ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception(' └── Failed to create project database: ' . $e->getMessage());
}
sleep($sleep);
}
}
if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForProject);
$audit->setup();
}
@@ -441,8 +456,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
});
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
Http::setResource('swooleRequest', fn () => $swooleRequest);
Http::setResource('swooleResponse', fn () => $swooleResponse);
App::setResource('swooleRequest', fn () => $swooleRequest);
App::setResource('swooleResponse', fn () => $swooleResponse);
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
@@ -459,20 +475,16 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
return;
}
$app = new Http('UTC');
$app = new App('UTC');
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
$pools = $register->get('pools');
Http::setResource('pools', fn () => $pools);
App::setResource('pools', fn () => $pools);
try {
$authorization = $app->getResource('authorization');
$request->setAuthorization($authorization);
$response->setAuthorization($authorization);
$authorization->cleanRoles();
$authorization->addRole(Role::any()->toString());
Authorization::cleanRoles();
Authorization::setRole(Role::any()->toString());
$app->run($request, $response);
} catch (\Throwable $th) {
@@ -514,7 +526,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$log->addExtra('file', $th->getFile());
$log->addExtra('line', $th->getLine());
$log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('roles', isset($authorization) ? $authorization->getRoles() : []);
$log->addExtra('roles', Authorization::getRoles());
$sdk = $route->getLabel("sdk", false);
@@ -546,7 +558,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$swooleResponse->setStatusCode(500);
$output = ((Http::isDevelopment())) ? [
$output = ((App::isDevelopment())) ? [
'message' => 'Error: ' . $th->getMessage(),
'code' => 500,
'file' => $th->getFile(),
@@ -567,13 +579,13 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
Http::setResource('pools', fn () => $pools);
$app = new Http('UTC');
App::setResource('pools', fn () => $pools);
$app = new App('UTC');
/** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform');
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) {
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
try {
$time = DateTime::now();
$limit = 1000;
@@ -585,47 +597,22 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
if ($latestDocument !== null) {
$queries[] = Query::cursorAfter($latestDocument);
}
if ($lastSyncUpdate != null) {
if ($lastSyncUpdate !== null) {
$queries[] = Query::greaterThanEqual('$updatedAt', $lastSyncUpdate);
}
$results = [];
try {
$authorization = $app->getResource('authorization');
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
} catch (Throwable $th) {
Console::error($th->getMessage());
Console::error('rules ' . $th->getMessage());
}
$sum = count($results);
foreach ($results as $document) {
$domain = $document->getAttribute('domain');
$denyDomains = [];
$denyEnvVars = [
System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''),
System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
System::getEnv('_APP_DOMAIN_SITES', ''),
];
foreach ($denyEnvVars as $denyEnvVar) {
foreach (\explode(',', $denyEnvVar) as $denyDomain) {
if (empty($denyDomain)) {
continue;
}
$denyDomains[] = $denyDomain;
}
}
$isDenyDomain = false;
foreach ($denyDomains as $denyDomain) {
if (str_ends_with($domain, $denyDomain)) {
$isDenyDomain = true;
}
}
if ($isDenyDomain) {
if (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS')) || str_ends_with($domain, System::getEnv('_APP_DOMAIN_SITES'))) {
continue;
}
$domains->set(md5($domain), ['value' => 1]);
}
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
-1
View File
@@ -26,7 +26,6 @@ require_once __DIR__ . '/init/database/filters.php';
require_once __DIR__ . '/init/database/formats.php';
require_once __DIR__ . '/init/locales.php';
require_once __DIR__ . '/init/registers.php';
require_once __DIR__ . '/init/models.php';
require_once __DIR__ . '/init/resources.php';
\stream_context_set_default([ // Set global user agent and http settings
+1 -3
View File
@@ -22,9 +22,7 @@ Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapt
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
Config::load('projectScopes', __DIR__ . '/../config/scopes/project.php', $configAdapter);
Config::load('organizationScopes', __DIR__ . '/../config/scopes/organization.php', $configAdapter);
Config::load('accountScopes', __DIR__ . '/../config/scopes/account.php', $configAdapter);
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
+25 -17
View File
@@ -5,8 +5,6 @@ use Appwrite\Platform\Modules\Compute\Specification;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
const APP_VIEWS_DIR = __DIR__ . '/../views';
// Email
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = ''; // Default security email address
@@ -46,7 +44,7 @@ const APP_RESOURCE_TOKEN_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 = 4321;
const APP_VERSION_STABLE = '1.8.1';
const APP_VERSION_STABLE = '1.8.0';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -163,9 +161,6 @@ const ACTIVITY_TYPE_GUEST = 'guest';
const MFA_RECENT_DURATION = 1800; // 30 mins
// Database name
const APP_DATABASE = 'appwrite';
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
@@ -183,10 +178,8 @@ const BUILD_TYPE_DEPLOYMENT = 'deployment';
const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const ENABLE_EXECUTIONS_LIMIT_ON_ROUTE = false;
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_TRANSACTION = 'transaction';
@@ -196,10 +189,8 @@ const DELETE_TYPE_SITES = 'sites';
const DELETE_TYPE_FUNCTIONS = 'functions';
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
const DELETE_TYPE_USERS = 'users';
const DELETE_TYPE_TEAMS = 'teams';
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_EXECUTIONS_LIMIT = 'executionsLimit';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_USAGE = 'usage';
@@ -252,8 +243,6 @@ const MESSAGE_TYPE_PUSH = 'push';
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_DYNAMIC = 'dynamic';
const API_KEY_ORGANIZATION = 'organization';
const API_KEY_ACCOUNT = 'account';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
@@ -286,6 +275,22 @@ 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';
// documentsdb
const METRIC_DATABASES_DOCUMENTSDB = 'documentsdb.databases';
const METRIC_COLLECTIONS_DOCUMENTSDB = 'documentsdb.collections';
const METRIC_DATABASES_STORAGE_DOCUMENTSDB = 'documentsdb.databases.storage';
const METRIC_DATABASE_ID_COLLECTIONS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.collections';
const METRIC_DATABASE_ID_STORAGE_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.storage';
const METRIC_DOCUMENTS_DOCUMENTSDB = 'documentsdb.documents';
const METRIC_DATABASE_ID_DOCUMENTS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.{collectionInternalId}.documents';
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.{collectionInternalId}.databases.storage';
const METRIC_DATABASES_OPERATIONS_READS_DOCUMENTSDB = 'documentsdb.databases.operations.reads';
const METRIC_DATABASE_ID_OPERATIONS_READS_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.operations.reads';
const METRIC_DATABASES_OPERATIONS_WRITES_DOCUMENTSDB = 'documentsdb.databases.operations.writes';
const METRIC_DATABASE_ID_OPERATIONS_WRITES_DOCUMENTSDB = 'documentsdb.{databaseInternalId}.databases.operations.writes';
const METRIC_BUCKETS = 'buckets';
const METRIC_FILES = 'files';
const METRIC_FILES_STORAGE = 'files.storage';
@@ -371,7 +376,6 @@ const RESOURCE_TYPE_TOPICS = 'topics';
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
const RESOURCE_TYPE_EXECUTIONS = 'executions';
const RESOURCE_TYPE_VCS = 'vcs';
// Resource types for Tokens
const TOKENS_RESOURCE_TYPE_FILES = 'files';
@@ -387,6 +391,10 @@ const SCHEDULE_RESOURCE_TYPE_MESSAGE = 'message';
/** Preview cookie */
const COOKIE_NAME_PREVIEW = 'a_jwt_console';
// Cache Reconnect
const CACHE_RECONNECT_MAX_RETRIES = 2;
const CACHE_RECONNECT_RETRY_DELAY = 1000;
// Database types
const DATABASE_TYPE_LEGACY = 'legacy';
const DATABASE_TYPE_TABLESDB = 'tablesdb';
const DATABASE_TYPE_DOCUMENTSDB = 'documentsdb';
// CSV import/export allowed database types
const CSV_ALLOWED_DATABASE_TYPES = [DATABASE_TYPE_LEGACY, DATABASE_TYPE_LEGACY];
+32 -57
View File
@@ -4,6 +4,7 @@ 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(
@@ -69,11 +70,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
$attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
$attributes = $database->find('attributes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForAttributes()),
]));
Query::limit(PHP_INT_MAX),
]);
foreach ($attributes as $attribute) {
$attributeType = $attribute->getAttribute('type');
@@ -104,12 +105,12 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('indexes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForIndexes()),
]));
]);
}
);
@@ -119,11 +120,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('platforms', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -133,12 +134,17 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('keys', [
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::or([
Query::equal('projectInternalId', [$document->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$document->getSequence()]),
])
]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -148,11 +154,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('devKeys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -162,11 +168,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('webhooks', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -176,7 +182,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
return Authorization::skip(fn () => $database->find('sessions', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
@@ -189,7 +195,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('tokens', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -203,7 +209,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('challenges', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -217,7 +223,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('authenticators', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -231,7 +237,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('memberships', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -251,14 +257,14 @@ Database::addFilter(
default => ['function', 'site']
};
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('variables', [
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::equal('resourceType', $resourceType),
Query::orderAsc('resourceType'),
Query::orderAsc(),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -294,11 +300,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('variables', [
Query::equal('resourceType', ['project']),
Query::limit(APP_LIMIT_SUBQUERY)
]));
]);
}
);
@@ -331,7 +337,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('targets', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY)
@@ -345,7 +351,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
$targetIds = $database->getAuthorization()->skip(fn () => \array_map(
$targetIds = Authorization::skip(fn () => \array_map(
fn ($document) => $document->getAttribute('targetInternalId'),
$database->find('subscribers', [
Query::equal('topicInternalId', [$document->getSequence()]),
@@ -433,34 +439,3 @@ Database::addFilter(
return $value;
}
);
Database::addFilter(
'subQueryOrganizationKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['teams']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryAccountKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['users']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
-360
View File
@@ -1,360 +0,0 @@
<?php
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Account;
use Appwrite\Utopia\Response\Model\AlgoArgon2;
use Appwrite\Utopia\Response\Model\AlgoBcrypt;
use Appwrite\Utopia\Response\Model\AlgoMd5;
use Appwrite\Utopia\Response\Model\AlgoPhpass;
use Appwrite\Utopia\Response\Model\AlgoScrypt;
use Appwrite\Utopia\Response\Model\AlgoScryptModified;
use Appwrite\Utopia\Response\Model\AlgoSha;
use Appwrite\Utopia\Response\Model\Any;
use Appwrite\Utopia\Response\Model\Attribute;
use Appwrite\Utopia\Response\Model\AttributeBoolean;
use Appwrite\Utopia\Response\Model\AttributeDatetime;
use Appwrite\Utopia\Response\Model\AttributeEmail;
use Appwrite\Utopia\Response\Model\AttributeEnum;
use Appwrite\Utopia\Response\Model\AttributeFloat;
use Appwrite\Utopia\Response\Model\AttributeInteger;
use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeLine;
use Appwrite\Utopia\Response\Model\AttributeList;
use Appwrite\Utopia\Response\Model\AttributeLongtext;
use Appwrite\Utopia\Response\Model\AttributeMediumtext;
use Appwrite\Utopia\Response\Model\AttributePoint;
use Appwrite\Utopia\Response\Model\AttributePolygon;
use Appwrite\Utopia\Response\Model\AttributeRelationship;
use Appwrite\Utopia\Response\Model\AttributeString;
use Appwrite\Utopia\Response\Model\AttributeText;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\AttributeVarchar;
use Appwrite\Utopia\Response\Model\AuthProvider;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Branch;
use Appwrite\Utopia\Response\Model\Bucket;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Column;
use Appwrite\Utopia\Response\Model\ColumnBoolean;
use Appwrite\Utopia\Response\Model\ColumnDatetime;
use Appwrite\Utopia\Response\Model\ColumnEmail;
use Appwrite\Utopia\Response\Model\ColumnEnum;
use Appwrite\Utopia\Response\Model\ColumnFloat;
use Appwrite\Utopia\Response\Model\ColumnIndex;
use Appwrite\Utopia\Response\Model\ColumnInteger;
use Appwrite\Utopia\Response\Model\ColumnIP;
use Appwrite\Utopia\Response\Model\ColumnLine;
use Appwrite\Utopia\Response\Model\ColumnList;
use Appwrite\Utopia\Response\Model\ColumnLongtext;
use Appwrite\Utopia\Response\Model\ColumnMediumtext;
use Appwrite\Utopia\Response\Model\ColumnPoint;
use Appwrite\Utopia\Response\Model\ColumnPolygon;
use Appwrite\Utopia\Response\Model\ColumnRelationship;
use Appwrite\Utopia\Response\Model\ColumnString;
use Appwrite\Utopia\Response\Model\ColumnText;
use Appwrite\Utopia\Response\Model\ColumnURL;
use Appwrite\Utopia\Response\Model\ColumnVarchar;
use Appwrite\Utopia\Response\Model\ConsoleVariables;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
use Appwrite\Utopia\Response\Model\Currency;
use Appwrite\Utopia\Response\Model\Database;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Utopia\Response\Model\DetectionFramework;
use Appwrite\Utopia\Response\Model\DetectionRuntime;
use Appwrite\Utopia\Response\Model\DetectionVariable;
use Appwrite\Utopia\Response\Model\DevKey;
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
use Appwrite\Utopia\Response\Model\Error;
use Appwrite\Utopia\Response\Model\ErrorDev;
use Appwrite\Utopia\Response\Model\Execution;
use Appwrite\Utopia\Response\Model\File;
use Appwrite\Utopia\Response\Model\Framework;
use Appwrite\Utopia\Response\Model\FrameworkAdapter;
use Appwrite\Utopia\Response\Model\Func;
use Appwrite\Utopia\Response\Model\Headers;
use Appwrite\Utopia\Response\Model\HealthAntivirus;
use Appwrite\Utopia\Response\Model\HealthCertificate;
use Appwrite\Utopia\Response\Model\HealthQueue;
use Appwrite\Utopia\Response\Model\HealthStatus;
use Appwrite\Utopia\Response\Model\HealthTime;
use Appwrite\Utopia\Response\Model\HealthVersion;
use Appwrite\Utopia\Response\Model\Identity;
use Appwrite\Utopia\Response\Model\Index;
use Appwrite\Utopia\Response\Model\Installation;
use Appwrite\Utopia\Response\Model\JWT;
use Appwrite\Utopia\Response\Model\Key;
use Appwrite\Utopia\Response\Model\Language;
use Appwrite\Utopia\Response\Model\Locale;
use Appwrite\Utopia\Response\Model\LocaleCode;
use Appwrite\Utopia\Response\Model\Log;
use Appwrite\Utopia\Response\Model\Membership;
use Appwrite\Utopia\Response\Model\Message;
use Appwrite\Utopia\Response\Model\Metric;
use Appwrite\Utopia\Response\Model\MetricBreakdown;
use Appwrite\Utopia\Response\Model\MFAChallenge;
use Appwrite\Utopia\Response\Model\MFAFactors;
use Appwrite\Utopia\Response\Model\MFARecoveryCodes;
use Appwrite\Utopia\Response\Model\MFAType;
use Appwrite\Utopia\Response\Model\Migration;
use Appwrite\Utopia\Response\Model\MigrationFirebaseProject;
use Appwrite\Utopia\Response\Model\MigrationReport;
use Appwrite\Utopia\Response\Model\Mock;
use Appwrite\Utopia\Response\Model\MockNumber;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\Phone;
use Appwrite\Utopia\Response\Model\Platform;
use Appwrite\Utopia\Response\Model\Preferences;
use Appwrite\Utopia\Response\Model\Project;
use Appwrite\Utopia\Response\Model\Provider;
use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
use Appwrite\Utopia\Response\Model\ResourceToken;
use Appwrite\Utopia\Response\Model\Row;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Session;
use Appwrite\Utopia\Response\Model\Site;
use Appwrite\Utopia\Response\Model\Specification;
use Appwrite\Utopia\Response\Model\Subscriber;
use Appwrite\Utopia\Response\Model\Table;
use Appwrite\Utopia\Response\Model\Target;
use Appwrite\Utopia\Response\Model\Team;
use Appwrite\Utopia\Response\Model\TemplateEmail;
use Appwrite\Utopia\Response\Model\TemplateFramework;
use Appwrite\Utopia\Response\Model\TemplateFunction;
use Appwrite\Utopia\Response\Model\TemplateRuntime;
use Appwrite\Utopia\Response\Model\TemplateSite;
use Appwrite\Utopia\Response\Model\TemplateSMS;
use Appwrite\Utopia\Response\Model\TemplateVariable;
use Appwrite\Utopia\Response\Model\Token;
use Appwrite\Utopia\Response\Model\Topic;
use Appwrite\Utopia\Response\Model\Transaction;
use Appwrite\Utopia\Response\Model\UsageBuckets;
use Appwrite\Utopia\Response\Model\UsageCollection;
use Appwrite\Utopia\Response\Model\UsageDatabase;
use Appwrite\Utopia\Response\Model\UsageDatabases;
use Appwrite\Utopia\Response\Model\UsageFunction;
use Appwrite\Utopia\Response\Model\UsageFunctions;
use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageSite;
use Appwrite\Utopia\Response\Model\UsageSites;
use Appwrite\Utopia\Response\Model\UsageStorage;
use Appwrite\Utopia\Response\Model\UsageTable;
use Appwrite\Utopia\Response\Model\UsageUsers;
use Appwrite\Utopia\Response\Model\User;
use Appwrite\Utopia\Response\Model\Variable;
use Appwrite\Utopia\Response\Model\VcsContent;
use Appwrite\Utopia\Response\Model\Webhook;
// General
Response::setModel(new None());
Response::setModel(new Any());
Response::setModel(new Error());
Response::setModel(new ErrorDev());
// Lists
Response::setModel(new BaseList('Rows List', Response::MODEL_ROW_LIST, 'rows', Response::MODEL_ROW));
Response::setModel(new BaseList('Documents List', Response::MODEL_DOCUMENT_LIST, 'documents', Response::MODEL_DOCUMENT));
Response::setModel(new BaseList('Tables List', Response::MODEL_TABLE_LIST, 'tables', Response::MODEL_TABLE));
Response::setModel(new BaseList('Collections List', Response::MODEL_COLLECTION_LIST, 'collections', Response::MODEL_COLLECTION));
Response::setModel(new BaseList('Databases List', Response::MODEL_DATABASE_LIST, 'databases', Response::MODEL_DATABASE));
Response::setModel(new BaseList('Indexes List', Response::MODEL_INDEX_LIST, 'indexes', Response::MODEL_INDEX));
Response::setModel(new BaseList('Column Indexes List', Response::MODEL_COLUMN_INDEX_LIST, 'indexes', Response::MODEL_COLUMN_INDEX));
Response::setModel(new BaseList('Users List', Response::MODEL_USER_LIST, 'users', Response::MODEL_USER));
Response::setModel(new BaseList('Sessions List', Response::MODEL_SESSION_LIST, 'sessions', Response::MODEL_SESSION));
Response::setModel(new BaseList('Identities List', Response::MODEL_IDENTITY_LIST, 'identities', Response::MODEL_IDENTITY));
Response::setModel(new BaseList('Logs List', Response::MODEL_LOG_LIST, 'logs', Response::MODEL_LOG));
Response::setModel(new BaseList('Files List', Response::MODEL_FILE_LIST, 'files', Response::MODEL_FILE));
Response::setModel(new BaseList('Buckets List', Response::MODEL_BUCKET_LIST, 'buckets', Response::MODEL_BUCKET));
Response::setModel(new BaseList('Resource Tokens List', Response::MODEL_RESOURCE_TOKEN_LIST, 'tokens', Response::MODEL_RESOURCE_TOKEN));
Response::setModel(new BaseList('Teams List', Response::MODEL_TEAM_LIST, 'teams', Response::MODEL_TEAM));
Response::setModel(new BaseList('Memberships List', Response::MODEL_MEMBERSHIP_LIST, 'memberships', Response::MODEL_MEMBERSHIP));
Response::setModel(new BaseList('Sites List', Response::MODEL_SITE_LIST, 'sites', Response::MODEL_SITE));
Response::setModel(new BaseList('Site Templates List', Response::MODEL_TEMPLATE_SITE_LIST, 'templates', Response::MODEL_TEMPLATE_SITE));
Response::setModel(new BaseList('Functions List', Response::MODEL_FUNCTION_LIST, 'functions', Response::MODEL_FUNCTION));
Response::setModel(new BaseList('Function Templates List', Response::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', Response::MODEL_TEMPLATE_FUNCTION));
Response::setModel(new BaseList('Installations List', Response::MODEL_INSTALLATION_LIST, 'installations', Response::MODEL_INSTALLATION));
Response::setModel(new BaseList('Framework Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK));
Response::setModel(new BaseList('Runtime Provider Repositories List', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', Response::MODEL_PROVIDER_REPOSITORY_RUNTIME));
Response::setModel(new BaseList('Branches List', Response::MODEL_BRANCH_LIST, 'branches', Response::MODEL_BRANCH));
Response::setModel(new BaseList('Frameworks List', Response::MODEL_FRAMEWORK_LIST, 'frameworks', Response::MODEL_FRAMEWORK));
Response::setModel(new BaseList('Runtimes List', Response::MODEL_RUNTIME_LIST, 'runtimes', Response::MODEL_RUNTIME));
Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_LIST, 'deployments', Response::MODEL_DEPLOYMENT));
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
Response::setModel(new BaseList('Platforms List', Response::MODEL_PLATFORM_LIST, 'platforms', Response::MODEL_PLATFORM, true, false));
Response::setModel(new BaseList('Countries List', Response::MODEL_COUNTRY_LIST, 'countries', Response::MODEL_COUNTRY));
Response::setModel(new BaseList('Continents List', Response::MODEL_CONTINENT_LIST, 'continents', Response::MODEL_CONTINENT));
Response::setModel(new BaseList('Languages List', Response::MODEL_LANGUAGE_LIST, 'languages', Response::MODEL_LANGUAGE));
Response::setModel(new BaseList('Currencies List', Response::MODEL_CURRENCY_LIST, 'currencies', Response::MODEL_CURRENCY));
Response::setModel(new BaseList('Phones List', Response::MODEL_PHONE_LIST, 'phones', Response::MODEL_PHONE));
Response::setModel(new BaseList('Metric List', Response::MODEL_METRIC_LIST, 'metrics', Response::MODEL_METRIC, true, false));
Response::setModel(new BaseList('Variables List', Response::MODEL_VARIABLE_LIST, 'variables', Response::MODEL_VARIABLE));
Response::setModel(new BaseList('Status List', Response::MODEL_HEALTH_STATUS_LIST, 'statuses', Response::MODEL_HEALTH_STATUS));
Response::setModel(new BaseList('Rule List', Response::MODEL_PROXY_RULE_LIST, 'rules', Response::MODEL_PROXY_RULE));
Response::setModel(new BaseList('Locale codes list', Response::MODEL_LOCALE_CODE_LIST, 'localeCodes', Response::MODEL_LOCALE_CODE));
Response::setModel(new BaseList('Provider list', Response::MODEL_PROVIDER_LIST, 'providers', Response::MODEL_PROVIDER));
Response::setModel(new BaseList('Message list', Response::MODEL_MESSAGE_LIST, 'messages', Response::MODEL_MESSAGE));
Response::setModel(new BaseList('Topic list', Response::MODEL_TOPIC_LIST, 'topics', Response::MODEL_TOPIC));
Response::setModel(new BaseList('Subscriber list', Response::MODEL_SUBSCRIBER_LIST, 'subscribers', Response::MODEL_SUBSCRIBER));
Response::setModel(new BaseList('Target list', Response::MODEL_TARGET_LIST, 'targets', Response::MODEL_TARGET));
Response::setModel(new BaseList('Transaction List', Response::MODEL_TRANSACTION_LIST, 'transactions', Response::MODEL_TRANSACTION));
Response::setModel(new BaseList('Migrations List', Response::MODEL_MIGRATION_LIST, 'migrations', Response::MODEL_MIGRATION));
Response::setModel(new BaseList('Migrations Firebase Projects List', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', Response::MODEL_MIGRATION_FIREBASE_PROJECT));
Response::setModel(new BaseList('Specifications List', Response::MODEL_SPECIFICATION_LIST, 'specifications', Response::MODEL_SPECIFICATION));
Response::setModel(new BaseList('VCS Content List', Response::MODEL_VCS_CONTENT_LIST, 'contents', Response::MODEL_VCS_CONTENT));
// Entities
Response::setModel(new Database());
// Collection API Models
Response::setModel(new Collection());
Response::setModel(new Attribute());
Response::setModel(new AttributeList());
Response::setModel(new AttributeString());
Response::setModel(new AttributeInteger());
Response::setModel(new AttributeFloat());
Response::setModel(new AttributeBoolean());
Response::setModel(new AttributeEmail());
Response::setModel(new AttributeEnum());
Response::setModel(new AttributeIP());
Response::setModel(new AttributeURL());
Response::setModel(new AttributeDatetime());
Response::setModel(new AttributeRelationship());
Response::setModel(new AttributePoint());
Response::setModel(new AttributeLine());
Response::setModel(new AttributePolygon());
Response::setModel(new AttributeVarchar());
Response::setModel(new AttributeText());
Response::setModel(new AttributeMediumtext());
Response::setModel(new AttributeLongtext());
// Table API Models
Response::setModel(new Table());
Response::setModel(new Column());
Response::setModel(new ColumnList());
Response::setModel(new ColumnString());
Response::setModel(new ColumnInteger());
Response::setModel(new ColumnFloat());
Response::setModel(new ColumnBoolean());
Response::setModel(new ColumnEmail());
Response::setModel(new ColumnEnum());
Response::setModel(new ColumnIP());
Response::setModel(new ColumnURL());
Response::setModel(new ColumnDatetime());
Response::setModel(new ColumnRelationship());
Response::setModel(new ColumnPoint());
Response::setModel(new ColumnLine());
Response::setModel(new ColumnPolygon());
Response::setModel(new ColumnVarchar());
Response::setModel(new ColumnText());
Response::setModel(new ColumnMediumtext());
Response::setModel(new ColumnLongtext());
Response::setModel(new Index());
Response::setModel(new ColumnIndex());
Response::setModel(new Row());
Response::setModel(new ModelDocument());
Response::setModel(new Log());
Response::setModel(new User());
Response::setModel(new AlgoMd5());
Response::setModel(new AlgoSha());
Response::setModel(new AlgoPhpass());
Response::setModel(new AlgoBcrypt());
Response::setModel(new AlgoScrypt());
Response::setModel(new AlgoScryptModified());
Response::setModel(new AlgoArgon2());
Response::setModel(new Account());
Response::setModel(new Preferences());
Response::setModel(new Session());
Response::setModel(new Identity());
Response::setModel(new Token());
Response::setModel(new JWT());
Response::setModel(new Locale());
Response::setModel(new LocaleCode());
Response::setModel(new File());
Response::setModel(new Bucket());
Response::setModel(new ResourceToken());
Response::setModel(new Team());
Response::setModel(new Membership());
Response::setModel(new Site());
Response::setModel(new TemplateSite());
Response::setModel(new TemplateFramework());
Response::setModel(new Func());
Response::setModel(new TemplateFunction());
Response::setModel(new TemplateRuntime());
Response::setModel(new TemplateVariable());
Response::setModel(new Installation());
Response::setModel(new ProviderRepository());
Response::setModel(new ProviderRepositoryFramework());
Response::setModel(new ProviderRepositoryRuntime());
Response::setModel(new DetectionFramework());
Response::setModel(new DetectionRuntime());
Response::setModel(new DetectionVariable());
Response::setModel(new VcsContent());
Response::setModel(new Branch());
Response::setModel(new Runtime());
Response::setModel(new Framework());
Response::setModel(new FrameworkAdapter());
Response::setModel(new Deployment());
Response::setModel(new Execution());
Response::setModel(new Project());
Response::setModel(new Webhook());
Response::setModel(new Key());
Response::setModel(new DevKey());
Response::setModel(new MockNumber());
Response::setModel(new AuthProvider());
Response::setModel(new Platform());
Response::setModel(new Variable());
Response::setModel(new Country());
Response::setModel(new Continent());
Response::setModel(new Language());
Response::setModel(new Currency());
Response::setModel(new Phone());
Response::setModel(new HealthAntivirus());
Response::setModel(new HealthQueue());
Response::setModel(new HealthStatus());
Response::setModel(new HealthCertificate());
Response::setModel(new HealthTime());
Response::setModel(new HealthVersion());
Response::setModel(new Metric());
Response::setModel(new MetricBreakdown());
Response::setModel(new UsageDatabases());
Response::setModel(new UsageDatabase());
Response::setModel(new UsageTable());
Response::setModel(new UsageCollection());
Response::setModel(new UsageUsers());
Response::setModel(new UsageStorage());
Response::setModel(new UsageBuckets());
Response::setModel(new UsageFunctions());
Response::setModel(new UsageFunction());
Response::setModel(new UsageSites());
Response::setModel(new UsageSite());
Response::setModel(new UsageProject());
Response::setModel(new Headers());
Response::setModel(new Specification());
Response::setModel(new Rule());
Response::setModel(new TemplateSMS());
Response::setModel(new TemplateEmail());
Response::setModel(new ConsoleVariables());
Response::setModel(new MFAChallenge());
Response::setModel(new MFARecoveryCodes());
Response::setModel(new MFAType());
Response::setModel(new MFAFactors());
Response::setModel(new Provider());
Response::setModel(new Message());
Response::setModel(new Topic());
Response::setModel(new Transaction());
Response::setModel(new Subscriber());
Response::setModel(new Target());
Response::setModel(new Migration());
Response::setModel(new MigrationReport());
Response::setModel(new MigrationFirebaseProject());
// Tests (keep last)
Response::setModel(new Mock());
+62 -37
View File
@@ -8,35 +8,34 @@ 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\Mongo;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Adapter\SQL;
use Utopia\Database\PDO;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Logger\Adapter\AppSignal;
use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Logger;
use Utopia\Pools\Adapter\Stack as StackPool;
use Utopia\Pools\Adapter\Swoole as SwoolePool;
use Utopia\Mongo\Client as MongoClient;
use Utopia\Pools\Group;
use Utopia\Pools\Pool;
use Utopia\Queue;
use Utopia\Registry\Registry;
use Utopia\System\System;
global $register;
$register = new Registry();
Http::setMode(System::getEnv('_APP_ENV', Http::MODE_TYPE_PRODUCTION));
App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
if (!Http::isProduction()) {
if (!App::isProduction()) {
// Allow specific domains to skip public domain validation in dev environment
// Useful for existing tests involving webhooks
PublicDomain::allow(['request-catcher-sms']);
@@ -150,9 +149,17 @@ $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'),
'scheme' => System::getEnv('_APP_DB_ADAPTER', 'mongodb'),
'host' => System::getEnv('_APP_DB_HOST', 'mongodb'),
'port' => System::getEnv('_APP_DB_PORT', '27017'),
'user' => System::getEnv('_APP_DB_USER', ''),
'pass' => System::getEnv('_APP_DB_PASS', ''),
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
]);
$fallbackForDocumentsDB = 'db_main=' . AppwriteURL::unparse([
'scheme' => System::getEnv('_APP_DB_ADAPTER_DOCUMENTSDB', 'mongodb'),
'host' => System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb'),
'port' => System::getEnv('_APP_DB_PORT_DOCUMENTSDB', '27017'),
'user' => System::getEnv('_APP_DB_USER', ''),
'pass' => System::getEnv('_APP_DB_PASS', ''),
'path' => System::getEnv('_APP_DB_SCHEMA', ''),
@@ -170,19 +177,25 @@ $register->set('pools', function () {
'type' => 'database',
'dsns' => $fallbackForDB,
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
'schemes' => ['mongodb','mariadb', 'mysql'],
],
'database' => [
'type' => 'database',
'dsns' => $fallbackForDB,
'multiple' => true,
'schemes' => ['mariadb', 'mysql'],
'schemes' => ['mongodb','mariadb', 'mysql'],
],
'documentsdb' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DATABASE_DOCUMENTSDB', $fallbackForDocumentsDB),
'multiple' => true,
'schemes' => ['mongodb'],
],
'logs' => [
'type' => 'database',
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
'multiple' => false,
'schemes' => ['mariadb', 'mysql'],
'schemes' => ['mongodb','mariadb', 'mysql'],
],
'publisher' => [
'type' => 'publisher',
@@ -262,6 +275,7 @@ $register->set('pools', function () {
*
* Resource assignment to an adapter will happen below.
*/
$resource = match ($dsnScheme) {
'mysql',
'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
@@ -275,6 +289,16 @@ $register->set('pools', function () {
]);
});
},
'mongodb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase, $dsn) {
try {
$mongo = new MongoClient($dsnDatabase, $dsnHost, (int)$dsnPort, $dsnUser, $dsnPass, false);
@$mongo->connect();
return $mongo;
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "MongoDB connection failed: " . $e->getMessage());
}
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new \Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
@@ -288,19 +312,21 @@ $register->set('pools', function () {
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
};
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
$pool = new Pool($poolAdapter, $name, $poolSize, function () use ($type, $resource, $dsn) {
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn, $key) {
// Get Adapter
switch ($type) {
case 'database':
$adapter = match ($dsn->getScheme()) {
'mariadb' => new MariaDB($resource()),
'mysql' => new MySQL($resource()),
'mongodb' => new Mongo($resource()),
default => null
};
$adapter->setDatabase($dsn->getPath());
if ($key === 'documentsdb') {
$adapter->setSupportForAttributes(false);
}
return $adapter;
case 'pubsub':
return match ($dsn->getScheme()) {
@@ -314,17 +340,10 @@ $register->set('pools', function () {
default => null
};
case 'cache':
$adapter = match ($dsn->getScheme()) {
return match ($dsn->getScheme()) {
'redis' => new RedisCache($resource()),
default => null
};
if ($adapter !== null) {
$adapter->setMaxRetries(CACHE_RECONNECT_MAX_RETRIES);
$adapter->setRetryDelay(CACHE_RECONNECT_RETRY_DELAY);
}
return $adapter;
default:
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
}
@@ -336,12 +355,6 @@ $register->set('pools', function () {
Config::setParam('pools-' . $key, $config);
}
$reconnectAttempts = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_ATTEMPTS', 5);
$reconnectSleep = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_SLEEP', 2);
$group->setReconnectAttempts($reconnectAttempts);
$group->setReconnectSleep($reconnectSleep);
return $group;
});
@@ -352,13 +365,27 @@ $register->set('db', function () {
$dbUser = System::getEnv('_APP_DB_USER', '');
$dbPass = System::getEnv('_APP_DB_PASS', '');
$dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
$dbAdapter = System::getEnv('_APP_DB_ADAPTER', 'mongodb');
$dsn = '';
return new PDO(
"mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
$dbUser,
$dbPass,
SQL::getPDOAttributes()
);
switch ($dbAdapter) {
case 'mongodb':
try {
$mongo = new MongoClient($dbScheme, $dbHost, (int)$dbPort, $dbUser, $dbPass, false);
@$mongo->connect();
return $mongo;
} catch (\Throwable $e) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "MongoDB connection failed: " . $e->getMessage());
}
case 'mysql':
case 'mariadb':
default:
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4";
return new PDO($dsn, $dbUser, $dbPass, SQL::getPDOAttributes());
}
});
$register->set('smtp', function () {
@@ -378,8 +405,6 @@ $register->set('smtp', function () {
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
$mail->SMTPAutoTLS = false;
$mail->CharSet = 'UTF-8';
$mail->Timeout = 10; /* Connection timeout */
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
$from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
$email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
+282 -454
View File
File diff suppressed because it is too large Load Diff
+40 -148
View File
@@ -16,6 +16,7 @@ use Swoole\Table;
use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
@@ -28,12 +29,11 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Http;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
@@ -67,7 +67,6 @@ if (!function_exists('getConsoleDB')) {
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, getCache());
$database
->setDatabase(APP_DATABASE)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', '_console');
@@ -124,7 +123,6 @@ if (!function_exists('getProjectDB')) {
}
$database
->setDatabase(APP_DATABASE)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
@@ -311,7 +309,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
'value' => '{}'
]);
$statsDocument = $database->getAuthorization()->skip(fn () => $database->createDocument('realtime', $document));
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
break;
} catch (Throwable) {
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
@@ -341,7 +339,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
->setAttribute('timestamp', DateTime::now())
->setAttribute('value', json_encode($payload));
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (Throwable $th) {
$logError($th, "updateWorkerDocument");
}
@@ -372,7 +370,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$payload = [];
$list = $database->getAuthorization()->skip(fn () => $database->find('realtime', [
$list = Authorization::skip(fn () => $database->find('realtime', [
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
]));
@@ -431,24 +429,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
]
];
$subscribers = $realtime->getSubscribers($event);
$groups = [];
foreach ($subscribers as $id => $matched) {
$key = implode(',', array_keys($matched));
$groups[$key]['ids'][] = $id;
$groups[$key]['subscriptions'] = array_keys($matched);
}
foreach ($groups as $group) {
$data = $event['data'];
$data['subscriptions'] = $group['subscriptions'];
$server->send($group['ids'], json_encode([
'type' => 'event',
'data' => $data
]));
}
$server->send($realtime->getSubscribers($event), json_encode([
'type' => 'event',
'data' => $event['data']
]));
}
});
@@ -480,70 +464,39 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
$consoleDatabase = getConsoleDB();
$project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$database = getProjectDB($project);
/** @var Appwrite\Utopia\Database\Documents\User $user */
$user = $database->getDocument('users', $userId);
$roles = $user->getRoles($database->getAuthorization());
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$meta = $realtime->getSubscriptionMetadata($connection);
$roles = $user->getRoles();
$channels = $realtime->connections[$connection]['channels'];
$realtime->unsubscribe($connection);
foreach ($meta as $subscriptionId => $subscription) {
$queries = Query::parseQueries($subscription['queries'] ?? []);
$realtime->subscribe(
$projectId,
$connection,
$subscriptionId,
$roles,
$subscription['channels'] ?? [],
$queries
);
}
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
$realtime->subscribe($projectId, $connection, $roles, $channels);
}
}
$receivers = $realtime->getSubscribers($event);
if (Http::isDevelopment() && !empty($receivers)) {
if (App::isDevelopment() && !empty($receivers)) {
Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers));
Console::log("[Debug][Worker {$workerId}] Connection IDs: " . json_encode(array_keys($receivers)));
Console::log("[Debug][Worker {$workerId}] Matched: " . json_encode(array_values($receivers)));
Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode($receivers));
Console::log("[Debug][Worker {$workerId}] Event: " . $payload);
}
// Group connections by matched subscription IDs for batch sending
$groups = [];
foreach ($receivers as $id => $matched) {
$key = implode(',', array_keys($matched));
$groups[$key]['ids'][] = $id;
$groups[$key]['subscriptions'] = array_keys($matched);
}
$total = 0;
foreach ($groups as $group) {
$data = $event['data'];
$data['subscriptions'] = $group['subscriptions'];
$server->send($group['ids'], json_encode([
$server->send(
$receivers,
json_encode([
'type' => 'event',
'data' => $data
]));
$total += count($group['ids']);
}
'data' => $event['data']
])
);
if ($total > 0) {
$register->get('telemetry.messageSentCounter')->add($total);
$stats->incr($event['project'], 'messages', $total);
if (($num = count($receivers)) > 0) {
$register->get('telemetry.messageSentCounter')->add($num);
$stats->incr($event['project'], 'messages', $num);
}
});
} catch (Throwable $th) {
@@ -560,20 +513,19 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
});
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) {
$app = new Http('UTC');
$app = new App('UTC');
$request = new Request($request);
$response = new Response(new SwooleResponse());
Console::info("Connection open (user: {$connection})");
Http::setResource('pools', fn () => $register->get('pools'));
Http::setResource('request', fn () => $request);
Http::setResource('response', fn () => $response);
App::setResource('pools', fn () => $register->get('pools'));
App::setResource('request', fn () => $request);
App::setResource('response', fn () => $response);
try {
/** @var Document $project */
$project = $app->getResource('project');
$authorization = $app->getResource('authorization');
/*
* Project Check
@@ -585,7 +537,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
if (
array_key_exists('realtime', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['realtime']
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -621,7 +573,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
$roles = $user->getRoles($authorization);
$roles = $user->getRoles();
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
@@ -632,42 +584,14 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
}
$names = array_keys($channels);
try {
$subscriptions = Realtime::constructSubscriptions(
$names,
fn ($channel) => $request->getQuery($channel, null)
);
} catch (QueryException $e) {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
}
$mapping = [];
foreach ($subscriptions as $index => $subscription) {
$subscriptionId = ID::unique();
$realtime->subscribe(
$project->getId(),
$connection,
$subscriptionId,
$roles,
$subscription['channels'],
$subscription['queries']
);
$mapping[$index] = $subscriptionId;
}
$realtime->connections[$connection]['authorization'] = $authorization;
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
'type' => 'connected',
'data' => [
'channels' => $names,
'subscriptions' => $mapping,
'channels' => array_keys($channels),
'user' => $user
]
]));
@@ -690,11 +614,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$code = 500;
}
$message = $th->getMessage();
// sanitize 0 && 5xx errors
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
$message = 'Error: Server Error';
}
@@ -709,7 +632,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->send([$connection], json_encode($response));
$server->close($connection, $code);
if (Http::isDevelopment()) {
if (App::isDevelopment()) {
Console::error('[Error] Connection Error');
Console::error('[Error] Code: ' . $response['data']['code']);
Console::error('[Error] Message: ' . $response['data']['message']);
@@ -720,19 +643,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try {
$response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
// Get authorization from connection (stored during onOpen)
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$projectId = $realtime->connections[$connection]['projectId'];
$database = getConsoleDB();
$database->setAuthorization($authorization);
if ($projectId !== 'console') {
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $database->getDocument('projects', $projectId));
$database = getProjectDB($project);
$database->setAuthorization($authorization);
} else {
$project = null;
}
@@ -796,33 +712,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
}
$roles = $user->getRoles($database->getAuthorization());
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
$meta = $realtime->getSubscriptionMetadata($connection);
$realtime->unsubscribe($connection);
if (!empty($projectId)) {
foreach ($meta as $subscriptionId => $subscription) {
$queries = Query::parseQueries($subscription['queries'] ?? []);
$realtime->subscribe(
$projectId,
$connection,
$subscriptionId,
$roles,
$subscription['channels'] ?? [],
$queries
);
}
}
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
$roles = $user->getRoles();
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
$user = $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
@@ -848,7 +740,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
$message = $th->getMessage();
// sanitize 0 && 5xx errors
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
if (($code === 0 || $code >= 500) && !App::isDevelopment()) {
$message = 'Error: Server Error';
}
+96 -33
View File
@@ -11,10 +11,11 @@ $httpsPort = $this->getParam('httpsPort', '');
$version = $this->getParam('version', '');
$organization = $this->getParam('organization', '');
$image = $this->getParam('image', '');
$enableAssistant = $this->getParam('enableAssistant', false);
$dbService = $this->getParam('database');
?>services:
traefik:
image: traefik:3.6
image: traefik:2.11
container_name: appwrite-traefik
<<: *x-logging
command:
@@ -70,7 +71,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite-sites:/storage/sites:rw
- appwrite-builds:/storage/builds:rw
depends_on:
- mariadb
- <?= $dbService . "\n" ?>
- redis
# - clamav
environment:
@@ -103,6 +104,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -165,7 +167,6 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_INTROSPECTION
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
@@ -181,7 +182,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:7.5.7
image: <?php echo $organization; ?>/console:7.4.7
restart: unless-stopped
networks:
- appwrite
@@ -223,7 +224,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
networks:
- appwrite
depends_on:
- mariadb
- <?= $dbService . "\n" ?>
- redis
environment:
- _APP_ENV
@@ -235,6 +236,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -253,7 +255,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -262,6 +264,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -279,13 +282,14 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -307,7 +311,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
@@ -323,6 +327,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -370,7 +375,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -379,6 +384,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -396,7 +402,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
volumes:
- appwrite-functions:/storage/functions:rw
- appwrite-sites:/storage/sites:rw
@@ -412,6 +418,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -464,7 +471,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -484,6 +491,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -501,7 +509,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
- openruntimes-executor
environment:
- _APP_ENV
@@ -513,6 +521,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -546,6 +555,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -584,6 +594,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -626,7 +637,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
volumes:
- appwrite-imports:/storage/imports:rw
depends_on:
- mariadb
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
@@ -642,6 +653,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -676,6 +688,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -700,11 +713,12 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -729,11 +743,12 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -757,11 +772,12 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
depends_on:
- redis
- mariadb
- <?= $dbService . "\n" ?>
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -784,7 +800,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
networks:
- appwrite
depends_on:
- mariadb
- <?= $dbService . "\n" ?>
- redis
environment:
- _APP_ENV
@@ -794,6 +810,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -809,7 +826,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
networks:
- appwrite
depends_on:
- mariadb
- <?= $dbService . "\n" ?>
- redis
environment:
- _APP_ENV
@@ -819,6 +836,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -834,7 +852,7 @@ $enableAssistant = $this->getParam('enableAssistant', false);
networks:
- appwrite
depends_on:
- mariadb
- <?= $dbService . "\n" ?>
- redis
environment:
- _APP_ENV
@@ -844,13 +862,13 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
<?php if ($enableAssistant): ?>
appwrite-assistant:
image: appwrite/assistant:0.8.4
container_name: appwrite-assistant
@@ -860,7 +878,6 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
<?php endif; ?>
appwrite-browser:
image: appwrite/browser:0.3.2
@@ -922,22 +939,63 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<?php if ($dbService === 'mariadb'): ?>
mariadb:
image: mariadb:10.11
container_name: appwrite-mariadb
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: 'mysqld --innodb-flush-method=fsync'
<?php elseif ($dbService === 'mongodb'): ?>
mongodb:
image: mongo:8.0.10
container_name: appwrite-mongodb
<<: *x-logging
restart: unless-stopped
networks:
- appwrite
volumes:
- appwrite-mariadb:/var/lib/mysql:rw
- appwrite-mongodb:/data/db
- appwrite-mongodb-keyfile:/data/keyfile
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
- ./mongo-entrypoint.sh:/mongo-entrypoint.sh:ro
ports:
- "27017:27017"
environment:
- MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
command: 'mysqld --innodb-flush-method=fsync'
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MONGO_INITDB_DATABASE=${_APP_DB_SCHEMA}
- MONGO_INITDB_USERNAME=${_APP_DB_USER}
- MONGO_INITDB_PASSWORD=${_APP_DB_PASS}
entrypoint: ["/bin/bash", "/mongo-entrypoint.sh"]
healthcheck:
test: |
bash -c "
if mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval 'rs.status().ok' 2>/dev/null | grep -q 1; then
exit 0
else
mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval \"
rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongodb:27017'}]})
\" 2>/dev/null || exit 1
fi
"
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
<?php endif; ?>
redis:
image: redis:7.2.4-alpine
@@ -972,7 +1030,12 @@ networks:
name: runtimes
volumes:
<?php if ($dbService === 'mariadb'): ?>
appwrite-mariadb:
<?php elseif ($dbService === 'mongodb'): ?>
appwrite-mongodb:
appwrite-mongodb-keyfile:
<?php endif; ?>
appwrite-redis:
appwrite-cache:
appwrite-uploads:
+66 -78
View File
@@ -9,13 +9,11 @@ use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Execution;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
@@ -23,8 +21,6 @@ use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit as UtopiaAudit;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -45,42 +41,24 @@ use Utopia\Queue\Message;
use Utopia\Queue\Publisher;
use Utopia\Queue\Server;
use Utopia\Registry\Registry;
use Utopia\Span\Exporter;
use Utopia\Span\Span;
use Utopia\Span\Storage;
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
Authorization::disable();
Runtime::enableCoroutine();
Span::setStorage(new Storage\Coroutine());
Span::addExporter(new Exporter\Stdout());
global $register;
Server::setResource('register', fn () => $register);
Server::setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
$pools = $register->get('pools');
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class)
;
$dbForPlatform->setNamespace('_console');
$dbForPlatform->setDocumentType('users', User::class);
return $dbForPlatform;
}, ['cache', 'register', 'authorization']);
}, ['cache', 'register']);
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? [];
@@ -93,7 +71,7 @@ Server::setResource('project', function (Message $message, Database $dbForPlatfo
return $dbForPlatform->getDocument('projects', $project->getId());
}, ['message', 'dbForPlatform']);
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform, Authorization $authorization) {
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -125,18 +103,15 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
Server::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, $authorization, &$databases): Database {
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -150,7 +125,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$database->setAuthorization($authorization);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
@@ -187,18 +162,15 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
}, ['pools', 'dbForPlatform', 'cache']);
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
return $database;
@@ -208,8 +180,6 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
@@ -222,7 +192,50 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
return $database;
};
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register, Document $project) {
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project): Database {
$projectDocument ??= $project;
$databaseType = $database->getAttribute('database', '');
try {
$databaseDSN = new DSN($databaseType);
} catch (\InvalidArgumentException) {
$databaseDSN = new DSN('mysql://'.$databaseType);
}
try {
$dsn = new DSN($projectDocument->getAttribute('database'));
} catch (\InvalidArgumentException) {
// Temporary fallback until all projects use shared tables
$dsn = new DSN('mysql://' . $projectDocument->getAttribute('database'));
}
$pools = $register->get('pools');
$pool = $pools->get($databaseDSN->getHost());
$adapter = new DatabasePool($pool);
$database = new Database($adapter, $cache);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables, true)) {
$database
->setSharedTables(true)
->setTenant((int) $projectDocument->getSequence())
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $projectDocument->getSequence());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['cache', 'register', 'project']);
Server::setResource('abuseRetention', function () {
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
@@ -335,10 +348,6 @@ Server::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
Server::setResource('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
Server::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
@@ -359,10 +368,6 @@ Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
Server::setResource('queueForExecutions', function (Publisher $publisher) {
return new Execution($publisher);
}, ['publisher']);
Server::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
@@ -488,27 +493,6 @@ Server::setResource('logError', function (Registry $register, Document $project)
Server::setResource('executor', fn () => new Executor());
Server::setResource('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
if ($project->isEmpty() || $project->getId() === 'console') {
$adapter = new AdapterDatabase($dbForPlatform);
return new UtopiaAudit($adapter);
}
$dbForProject = $getProjectDB($project);
$adapter = new AdapterDatabase($dbForProject);
return new UtopiaAudit($adapter);
};
}, ['dbForPlatform', 'getProjectDB']);
Server::setResource('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
$pools = $register->get('pools');
$platform = new Appwrite();
$args = $platform->getEnv('argv');
@@ -553,8 +537,7 @@ $worker
->inject('log')
->inject('pools')
->inject('project')
->inject('authorization')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($worker, $queueName) {
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
@@ -570,7 +553,7 @@ $worker
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', $authorization->getRoles());
$log->addExtra('roles', Authorization::getRoles());
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
@@ -589,4 +572,9 @@ $worker
Console::error('[Error] Line: ' . $error->getLine());
});
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->start();
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php executions "$@"
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php screenshots "$@"
+24 -22
View File
@@ -29,6 +29,8 @@
"Appwrite\\Tests\\": "tests/extensions"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=8.3.0",
"ext-curl": "*",
@@ -38,60 +40,60 @@
"ext-yaml": "*",
"ext-dom": "*",
"ext-redis": "*",
"ext-swoole": "6.*",
"ext-swoole": "*",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-zlib": "*",
"ext-sockets": "*",
"appwrite/php-runtimes": "0.19.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.2.*",
"utopia-php/abuse": "1.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "2.2.*",
"utopia-php/audit": "1.*",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "1.0.*",
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "1.*",
"utopia-php/database": "5.*",
"utopia-php/config": "1.*.*",
"utopia-php/database": "3.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "1.*",
"utopia-php/domains": "0.9.*",
"utopia-php/emails": "0.6.*",
"utopia-php/dns": "1.5.*",
"utopia-php/dns": "1.4.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.5.*",
"utopia-php/fetch": "0.4.*",
"utopia-php/image": "0.8.*",
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.5.*",
"utopia-php/migration": "1.4.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "1.*",
"utopia-php/pools": "0.8.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.15.*",
"utopia-php/queue": "0.11.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "1.*",
"utopia-php/system": "0.10.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/vcs": "1.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
"utopia-php/system": "0.9.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/vcs": "0.13.*",
"utopia-php/websocket": "0.3.*",
"matomo/device-detector": "6.4.*",
"dragonmantank/cron-expression": "3.4.*",
"dragonmantank/cron-expression": "3.3.*",
"phpmailer/phpmailer": "6.9.*",
"chillerlan/php-qrcode": "4.4.*",
"chillerlan/php-qrcode": "4.3.*",
"adhocore/jwt": "1.1.*",
"spomky-labs/otphp": "^11.4.2",
"spomky-labs/otphp": "^10.0",
"webonyx/graphql-php": "14.11.*",
"league/csv": "9.24.*",
"league/csv": "9.14.*",
"enshrined/svg-sanitize": "0.22.*"
},
"require-dev": {
"ext-fileinfo": "*",
"appwrite/sdk-generator": "*",
"phpunit/phpunit": "9.*",
"swoole/ide-helper": "6.*",
"swoole/ide-helper": "5.1.2",
"phpstan/phpstan": "1.8.*",
"textalk/websocket": "1.5.*",
"laravel/pint": "1.*",
Generated
+623 -678
View File
File diff suppressed because it is too large Load Diff
+95 -149
View File
@@ -12,7 +12,7 @@ x-logging: &x-logging
services:
traefik:
image: traefik:3.6
image: traefik:2.11
<<: *x-logging
container_name: appwrite-traefik
command:
@@ -98,9 +98,9 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
# - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
- coredns
# - clamav
@@ -112,9 +112,7 @@ services:
- _APP_ENV
- _APP_EDITION
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOCALE
- _APP_COMPRESSION_ENABLED
- _APP_COMPRESSION_MIN_SIZE_BYTES
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
@@ -133,7 +131,6 @@ services:
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_TRUSTED_PROJECTS
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
@@ -144,6 +141,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -204,7 +202,6 @@ services:
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_INTROSPECTION
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
@@ -229,14 +226,13 @@ services:
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
- _APP_MIGRATION_HOST
extra_hosts:
- "host.docker.internal:host-gateway"
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:7.5.7
image: appwrite/console:7.4.11
restart: unless-stopped
networks:
- appwrite
@@ -283,7 +279,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
@@ -295,6 +291,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -304,7 +301,6 @@ services:
- _APP_LOGGING_CONFIG
- _APP_LOGGING_CONFIG_REALTIME
- _APP_DATABASE_SHARED_TABLES
- _APP_POOL_ADAPTER=swoole
appwrite-worker-audits:
entrypoint: worker-audits
@@ -318,16 +314,16 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -348,15 +344,15 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
- request-catcher-sms
- request-catcher-webhook
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -379,7 +375,7 @@ services:
- appwrite
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
@@ -392,12 +388,12 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -446,16 +442,16 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -477,15 +473,16 @@ services:
- appwrite-functions:/storage/functions:rw
- appwrite-sites:/storage/sites:rw
- appwrite-builds:/storage/builds:rw
- appwrite-uploads:/storage/uploads:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_BROWSER_HOST
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
@@ -493,6 +490,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -512,7 +510,6 @@ services:
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_TRUSTED_PROJECTS
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
@@ -540,66 +537,6 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
appwrite-worker-screenshots:
entrypoint: worker-screenshots
<<: *x-logging
container_name: appwrite-worker-screenshots
image: appwrite-dev
networks:
- appwrite
volumes:
- appwrite-uploads:/storage/uploads:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
# Specific
- _APP_BROWSER_HOST
# Basic
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOGGING_CONFIG
# Database
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DATABASE_SHARED_TABLES
# Storage
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_S3_ENDPOINT
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
extra_hosts:
- "host.docker.internal:host-gateway"
appwrite-worker-certificates:
entrypoint: worker-certificates
<<: *x-logging
@@ -609,7 +546,7 @@ services:
- appwrite
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -618,7 +555,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -633,37 +569,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-executions:
entrypoint: worker-executions
<<: *x-logging
container_name: appwrite-worker-executions
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
redis:
condition: service_started
mariadb:
condition: service_started
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -683,16 +589,12 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
redis:
condition: service_started
mariadb:
condition: service_started
openruntimes-executor:
condition: service_healthy
- redis
- ${_APP_DB_HOST:-mongodb}
- openruntimes-executor
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
@@ -700,6 +602,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -736,7 +639,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
@@ -771,12 +673,12 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -825,11 +727,10 @@ services:
- ./src:/usr/src/code/src
- ./tests:/usr/src/code/tests
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -842,6 +743,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -852,7 +754,6 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_DATABASE_SHARED_TABLES
- _APP_OPTIONS_FORCE_HTTPS
- _APP_MIGRATION_HOST
appwrite-task-maintenance:
entrypoint: maintenance
@@ -870,7 +771,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -883,6 +783,7 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -915,7 +816,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -936,7 +836,6 @@ services:
- _APP_DB_PASS
- _APP_DATABASE_SHARED_TABLES
- _APP_INTERVAL_DOMAIN_VERIFICATION
- _APP_INTERVAL_CLEANUP_STALE_EXECUTIONS
appwrite-task-stats-resources:
container_name: appwrite-task-stats-resources
@@ -950,12 +849,12 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -982,12 +881,12 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -1014,12 +913,12 @@ services:
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
- ${_APP_DB_HOST:-mongodb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -1045,17 +944,17 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -1074,17 +973,17 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -1102,17 +1001,17 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- ${_APP_DB_HOST:-mongodb}
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@@ -1186,12 +1085,6 @@ services:
- OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
healthcheck:
test: ["CMD-SHELL", "curl -fsS -H \"Authorization: Bearer $$OPR_EXECUTOR_SECRET\" http://localhost/v1/health >/dev/null"]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
@@ -1211,6 +1104,57 @@ services:
- MARIADB_AUTO_UPGRADE=1
command: "mysqld --innodb-flush-method=fsync"
mongodb:
image: mongo:8.0.10
container_name: appwrite-mongodb
<<: *x-logging
networks:
- appwrite
volumes:
- appwrite-mongodb:/data/db
- appwrite-mongodb-keyfile:/data/keyfile
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
- ./mongo-entrypoint.sh:/mongo-entrypoint.sh:ro
ports:
- "27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
- MONGO_INITDB_DATABASE=${_APP_DB_SCHEMA}
- MONGO_INITDB_USERNAME=${_APP_DB_USER}
- MONGO_INITDB_PASSWORD=${_APP_DB_PASS}
entrypoint: ["/bin/bash", "/mongo-entrypoint.sh"]
healthcheck:
test: |
bash -c "
if mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval 'rs.status().ok' 2>/dev/null | grep -q 1; then
exit 0
else
mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval \"
rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'appwrite-mongodb:27017'}]})
\" 2>/dev/null || exit 1
fi
"
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
appwrite-mongo-express:
image: mongo-express
container_name: appwrite-mongo-express
networks:
- appwrite
ports:
- "8082:8081"
environment:
ME_CONFIG_MONGODB_URL: "mongodb://root:${_APP_DB_ROOT_PASS}@appwrite-mongodb:27017/?replicaSet=rs0&directConnection=true"
ME_CONFIG_BASICAUTH_USERNAME: ${_APP_DB_USER}
ME_CONFIG_BASICAUTH_PASSWORD: ${_APP_DB_PASS}
depends_on:
- mongodb
redis:
image: redis:7.2.4-alpine
<<: *x-logging
@@ -1438,6 +1382,8 @@ configs:
volumes:
appwrite-mariadb:
appwrite-mongodb:
appwrite-mongodb-keyfile:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
@@ -1446,4 +1392,4 @@ volumes:
appwrite-functions:
appwrite-sites:
appwrite-builds:
appwrite-config:
appwrite-config:
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -17,4 +16,3 @@ account.createAnonymousSession(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,4 +21,3 @@ account.createEmailPasswordSession(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -23,4 +22,3 @@ account.createEmailToken(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,4 +20,3 @@ account.createEmailVerification(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -9,16 +8,11 @@ Client client = new Client(context)
Account account = new Account(client);
account.createJWT(
0, // duration (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
account.createJWT(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
```
Log.d("Appwrite", result.toString());
}));
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -24,4 +23,3 @@ account.createMagicURLToken(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,4 +21,3 @@ account.createMFAAuthenticator(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,4 +21,3 @@ account.createMFAChallenge(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -17,4 +16,3 @@ account.createMFARecoveryCodes(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -25,4 +24,3 @@ account.createOAuth2Session(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -25,4 +24,3 @@ account.createOAuth2Token(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,4 +21,3 @@ account.createPhoneToken(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -17,4 +16,3 @@ account.createPhoneVerification(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -23,4 +22,3 @@ account.createPushTarget(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,4 +21,3 @@ account.createRecovery(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,4 +21,3 @@ account.createSession(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,4 +20,3 @@ account.createVerification(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -24,4 +23,3 @@ account.create(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,4 +20,3 @@ account.deleteIdentity(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -22,4 +21,3 @@ account.deleteMFAAuthenticator(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,4 +20,3 @@ account.deletePushTarget(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,4 +20,3 @@ account.deleteSession(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -17,4 +16,3 @@ account.deleteSessions(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -17,4 +16,3 @@ account.getMFARecoveryCodes(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -17,4 +16,3 @@ account.getPrefs(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -21,4 +20,3 @@ account.getSession(
})
);
```
@@ -1,4 +1,3 @@
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Account;
@@ -17,4 +16,3 @@ account.get(new CoroutineCallback<>((result, error) -> {
Log.d("Appwrite", result.toString());
}));
```

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