Compare commits

..
Author SHA1 Message Date
loks0n 633e898042 default spec 2024-11-22 17:18:19 +00:00
Christy Jacob a7faf656a8 Merge branch '1.6.x' of github.com:appwrite/appwrite into feat-custom-cf-hostnames 2024-11-22 12:42:38 +04:00
Christy JacobandGitHub 7ccc74bae2 Merge pull request #9006 from appwrite/fix-team-invite-session-creation
fix: team invites with existing session
2024-11-22 09:38:40 +01:00
Christy JacobandGitHub 44a0469d18 Merge branch '1.6.x' into fix-team-invite-session-creation 2024-11-22 09:21:54 +01:00
Jake Barnby e1b71df757 Merge remote-tracking branch 'origin/1.6.x' into feat-custom-cf-hostnames 2024-11-21 22:44:24 +13:00
Christy JacobandGitHub 34d8a0504b Merge pull request #9013 from appwrite/feat-db-create-on-server-start
Improved shared tables V2
2024-11-21 10:41:52 +01:00
Jake Barnby f9e518b2b0 Fix shared tables v2 deletes 2024-11-21 16:49:49 +13:00
Jake Barnby 8410710005 Remove redundant DSN construction 2024-11-21 16:47:12 +13:00
Jake Barnby 018c865d04 Skip all project db calls on project create if shared tables v2 2024-11-21 16:46:36 +13:00
Jake Barnby 415dedc135 Create V2 shared tables databases on server start 2024-11-21 16:46:10 +13:00
Jake Barnby b673c54cf9 Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-11-21 14:25:14 +13:00
loks0nandJake Barnby d257fdb045 chore: remove redundant compression 2024-11-21 14:24:56 +13:00
Jake Barnby 2f77d53380 Merge remote-tracking branch 'origin/1.6.x' into feat-custom-cf-hostnames 2024-11-21 14:20:28 +13:00
Steven NguyenandJake Barnby f6e7c12b02 Merge pull request #8922 from appwrite/lohanidamodar-patch-4
Improve compression param checks
2024-11-21 14:17:52 +13:00
Christy JacobandJake Barnby 908adebf74 Merge pull request #8964 from appwrite/debug-whitelist-console
debug: whitelist console
2024-11-21 14:11:20 +13:00
Jake Barnby e276532950 Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-11-21 14:09:42 +13:00
Christy JacobandJake Barnby 6e6899c1e9 Merge pull request #8988 from appwrite/fix-deletes
Fix project type delete checks
2024-11-21 14:09:02 +13:00
Christy JacobandJake Barnby f490d4edaf Merge pull request #8797 from appwrite/feat-migration
Feat migration
# Conflicts:
#	composer.lock
2024-11-21 14:08:20 +13:00
Fabian Gruber d32a1c35ab chore: update base to 0.9.5 2024-11-19 17:20:09 +01:00
Torsten Dittmann c97a94ec98 chore: run formatter 2024-11-19 17:18:51 +01:00
Torsten Dittmann 5f56c45c6c fix: team invites with existing session 2024-11-19 17:18:51 +01:00
Christy JacobandGitHub 218598e856 Merge pull request #9004 from appwrite/revert-8962-feat-fix-web-routes
Revert "feat fix web routes"
2024-11-19 10:38:51 +01:00
Christy JacobandGitHub fc3fa77056 Revert "feat fix web routes" 2024-11-19 13:35:37 +04:00
Christy JacobandGitHub 9a7e5a3153 Merge pull request #8988 from appwrite/fix-deletes
Fix project type delete checks
2024-11-15 13:14:14 +01:00
Jake Barnby a470e2b517 Output test mode 2024-11-15 18:55:52 +13:00
Jake Barnby 6851461a3b Fix workflow 2024-11-15 18:40:06 +13:00
Jake Barnby 24079a836b Fix project type delete checks 2024-11-15 18:37:16 +13:00
Fabian Gruber a3bd8b1e4e Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-11-13 14:11:49 +01:00
Christy JacobandGitHub 69b4ef4df1 Merge pull request #8964 from appwrite/debug-whitelist-console
debug: whitelist console
2024-11-12 17:51:57 +01:00
loks0n dad8f597ca debug: whitelist console 2024-11-12 16:05:51 +01:00
Luke B. SilverandGitHub 90f18c57ac Merge pull request #8962 from appwrite/feat-fix-web-routes
feat fix web routes
2024-11-12 12:42:30 +01:00
loks0n 6e7dd26f75 feat: fix web routes 2024-11-12 12:37:15 +01:00
Christy JacobandGitHub 9151abc951 Merge pull request #8797 from appwrite/feat-migration
Feat migration
2024-11-12 10:27:24 +01:00
Jake Barnby 714f6b62ac Merge remote-tracking branch 'origin/feat-custom-cf-hostnames' into feat-migration 2024-11-12 21:45:04 +13:00
Jake Barnby 316693798e Merge remote-tracking branch 'origin/1.6.x' into feat-custom-cf-hostnames 2024-11-12 21:44:50 +13:00
Jake Barnby c7f92fbb02 Merge remote-tracking branch 'origin/feat-custom-cf-hostnames' into feat-migration 2024-11-12 21:43:52 +13:00
Jake Barnby e715f9e90c Remove redundant V1 tests 2024-11-12 20:52:25 +13:00
Jake Barnby f6ea6d2fc5 Fix matrix 2024-11-12 19:15:33 +13:00
Jake Barnby e626c2ad45 Run in all table modes in parallel 2024-11-12 19:14:46 +13:00
Jake Barnby 3b19ef6b1e Remove V2 tests 2024-11-12 15:00:23 +13:00
Jake Barnby c1a3b29a97 Temp test all shared tables versions 2024-11-12 14:37:03 +13:00
Jake Barnby aa2385ffb9 Merge branch 'revert-revert-8946-chore-update-database' into feat-migration
# Conflicts:
#	composer.json
#	composer.lock
2024-11-12 13:36:05 +13:00
Jake Barnby 8b99517dd0 Merge remote-tracking branch 'origin/1.6.x' into feat-migration 2024-11-12 13:34:54 +13:00
Jake Barnby 33037a3ae5 Update database 2024-11-12 13:34:40 +13:00
Jake Barnby b0852aeca3 Update database 2024-11-11 23:15:00 +13:00
Jake Barnby 09da4378c6 Merge remote-tracking branch 'origin/1.6.x' into feat-migration 2024-11-11 23:13:35 +13:00
Jake Barnby a85b43654c Reapply "Update database"
This reverts commit b1a1ad618a.
2024-11-11 15:22:57 +13:00
Christy Jacob 76216c3bf6 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-11-09 16:01:16 +00:00
loks0n c8c4576bbc Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-11-09 13:07:15 +01:00
loks0n dac8766eb4 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-11-08 22:02:31 +01:00
loks0n f6de256220 chore: composer update 2024-11-08 21:50:33 +01:00
loks0n 2f9b44930f Revert "Merge pull request #8946 from appwrite/chore-update-database"
This reverts commit c0bd1664be, reversing
changes made to b42dd86451.
2024-11-08 21:48:27 +01:00
loks0n 627df0d11f chore: remove redundant compression 2024-11-08 20:24:55 +01:00
loks0n fcd323baa4 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-11-08 20:24:35 +01:00
loks0n 0009b73b06 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-11-08 16:53:22 +01:00
Jake Barnby 030e26b223 Merge remote-tracking branch 'origin/1.6.x' into feat-custom-cf-hostnames 2024-11-08 23:33:10 +13:00
Jake Barnby 9e69e41e02 Update lock 2024-11-08 17:04:29 +13:00
Jake Barnby 773fa6ffd6 Update lock 2024-11-08 16:34:07 +13:00
Jake Barnby 21bff77c50 Merge remote-tracking branch 'origin/feat-custom-cf-hostnames' into feat-migration
# Conflicts:
#	composer.json
#	composer.lock
2024-11-08 16:32:55 +13:00
loks0n 472f167b4a Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-11-07 20:31:41 +01:00
Fabian Gruber b4c986839e Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-11-06 12:40:56 +01:00
Damodar LohaniandGitHub d425c9f071 Merge pull request #8930 from appwrite/pathc-update-utopia-storage
upgrade utopia storage
2024-11-06 16:01:56 +05:45
Damodar Lohani e219ee2d84 upgrade utopia storage 2024-11-06 10:02:11 +00:00
Jake Barnby e66d0216c6 Merge remote-tracking branch 'origin/1.6.x' into feat-custom-cf-hostnames 2024-11-06 17:02:06 +13:00
Steven NguyenandGitHub e3e20d2923 Merge pull request #8922 from appwrite/lohanidamodar-patch-4
Improve compression param checks
2024-11-05 17:35:39 -08:00
loks0n e0c503e249 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-11-05 19:47:29 +01:00
Jake Barnby 228cabab3a Merge remote-tracking branch 'origin/1.6.x' into feat-custom-cf-hostnames 2024-11-05 23:38:24 +13:00
Damodar Lohani 028c5366c2 make compression nullable and assign default in action
- also update the `update bucket` endpoint
2024-11-05 08:12:58 +00:00
Damodar LohaniandGitHub cca0c3ae45 Make compression param whitelist strict 2024-11-05 12:59:11 +05:45
Jake Barnby 0b85a64dab Merge remote-tracking branch 'origin/1.6.x' into feat-custom-cf-hostnames 2024-11-04 22:44:25 +13:00
Jake Barnby 1633acbdc3 Merge remote-tracking branch 'origin/feat-custom-cf-hostnames' into feat-migration
# Conflicts:
#	composer.json
#	composer.lock
2024-11-01 16:16:06 +13:00
Jake BarnbyandGitHub 75e8635563 Merge pull request #8904 from appwrite/1.6.x
Merge 1.6.x into feat-custom-cf-hostnames
2024-10-31 22:12:38 +13:00
Jake Barnby 83e94629dc Merge remote-tracking branch 'origin/feat-custom-cf-hostnames' into feat-migration 2024-10-31 21:49:49 +13:00
Fabian Gruber 6e3160c502 Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-10-29 16:15:54 +01:00
Fabian Gruber 104ec3188f Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-10-29 15:36:38 +01:00
Fabian Gruber b47a4703c4 Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-10-25 13:39:12 +02:00
Fabian Gruber 4ceff430f9 Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-10-24 12:49:26 +02:00
Christy JacobandGitHub 5603516d88 Merge branch '1.6.x' into feat-custom-cf-hostnames 2024-10-23 23:56:43 +04:00
Jake Barnby 013d95ebf3 Update audit and abuse 2024-10-23 18:42:00 +13:00
Jake Barnby eca8d116c2 Fix attributes 2024-10-23 18:10:41 +13:00
Jake Barnby 8210808db0 Improve logic 2024-10-23 18:00:06 +13:00
Jake Barnby 4a003f2826 Force create audit/abuse metadata documents for shared tables v1 2024-10-23 17:41:44 +13:00
Christy Jacob 54f39494c0 Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-custom-cf-hostnames 2024-10-22 19:51:03 +00:00
Christy Jacob ae4edf119f chore: linter 2024-10-22 19:49:10 +00:00
Fabian GruberandChristy Jacob a56293e018 chore: replace 'Expires' with 'Cache-Control: private' header to avoid CDN caching 2024-10-22 12:18:58 +00:00
Jake Barnby a0c58317bd Merge remote-tracking branch 'origin/1.6.x' into feat-migration
# Conflicts:
#	composer.json
#	composer.lock
2024-10-22 15:13:45 +13:00
Jake Barnby d979c0c914 Create metadata document from outside if duplicate on shared tables V1 2024-10-21 22:43:47 +13:00
Christy Jacob 5ea83b8827 chore: update cert generation 2024-10-18 23:45:42 +00:00
Christy Jacob 6fe7d92fea chore: update cert generation 2024-10-18 22:32:03 +00:00
Christy Jacob 28b02a4ba8 chore: fix bug 2024-10-18 21:18:20 +00:00
Christy Jacob f4d456fa4c chore: updat certs logic 2024-10-18 20:52:29 +00:00
Fabian Gruber 687252b15b fix adding custom hostname & error logging 2024-10-18 22:21:53 +02:00
Fabian Gruber f21b1c87a6 fix missing ssl object 2024-10-18 22:07:36 +02:00
Fabian Gruber ccbde6c470 fix zone id 2024-10-18 21:44:36 +02:00
Torsten Dittmann f189c41421 temp: custom cloudflare hostnames 2024-10-18 21:39:16 +02:00
Jake BarnbyandGitHub 5fcb990b1c Merge branch '1.6.x' into feat-migration 2024-10-15 21:17:15 +13:00
Jake Barnby 30bd25d134 Update lock 2024-10-15 20:19:19 +13:00
Jake Barnby 36d2b84301 Update lock 2024-10-15 18:09:54 +13:00
Jake Barnby 78b9be2e78 Lint 2024-10-14 14:55:18 +13:00
Jake Barnby a3b7cf84d5 Allow shared tables V1 and V2 2024-10-14 14:50:56 +13:00
Jake Barnby add6a60af4 Remove redundant files 2024-10-14 14:33:53 +13:00
Jake Barnby 4543c182b5 Merge remote-tracking branch 'origin/1.6.x' into feat-migration
# Conflicts:
#	Dockerfile
#	composer.json
#	composer.lock
#	src/Appwrite/Platform/Workers/Deletes.php
2024-10-14 14:29:04 +13:00
Jake Barnby d18a67e6d8 Update packages 2024-09-12 23:06:47 +12:00
Jake Barnby 25d44b5b4c Update generator 2024-09-12 23:04:44 +12:00
Jake Barnby ee7de0cb41 Update database 2024-09-12 23:02:12 +12:00
Jake Barnby 28642b9f04 Shared tables array 2024-09-12 17:06:16 +12:00
Jake Barnby c18308c024 Update database 2024-08-27 17:43:29 +12:00
Jake Barnby 1eafbf2ee2 Update database 2024-08-19 21:21:20 +12:00
Jake Barnby b98bcdd4a1 Update database 2024-08-19 18:27:45 +12:00
Jake Barnby 4c8cd5b6c6 Update audit, abuse, database 2024-08-19 14:52:15 +12:00
Jake Barnby 5b37a91b46 Merge remote-tracking branch 'origin/1.5.x' into feat-migration 2024-08-13 22:11:18 +12:00
Jake Barnby 52647cef23 Merge remote-tracking branch 'origin/feat-single-shared-metadata-docs' into feat-migration
# Conflicts:
#	composer.json
#	composer.lock
2024-08-13 22:10:57 +12:00
Jake Barnby 11ec31aefb Update database 2024-08-13 21:49:25 +12:00
Jake Barnby 73ca1efbeb Catch duplicate db on project create 2024-08-13 21:49:14 +12:00
Jake Barnby 340aa48d93 Catch duplicate on console db create 2024-08-13 21:14:52 +12:00
Jake Barnby 8ff72b341e Don't set tenant for project collections creation 2024-08-12 23:46:34 +12:00
Jake Barnby a23402075e Update database 2024-08-12 17:45:43 +12:00
Jake Barnby 6b86ed0117 Merge remote-tracking branch 'origin/1.5.x' into feat-migration
# Conflicts:
#	composer.lock
2024-08-07 22:10:04 +12:00
Jake Barnby e2a45cec8e Merge branch '1.5.x' into feat-migration
# Conflicts:
#	composer.json
#	composer.lock
2024-08-05 21:13:42 +12:00
Jake Barnby ec2eb2bfa3 Revert to utopia database instead of extension 2024-07-02 19:10:02 +12:00
Jake Barnby 10adfc5124 Add mirror override 2024-07-02 14:55:13 +12:00
Jake Barnby 5f7aae0e60 Add missing import 2024-06-21 13:43:01 +12:00
Jake Barnby 4952608b8d Merge remote-tracking branch 'origin/refactor-usage-sn' into feat-migration 2024-06-21 13:39:51 +12:00
Jake Barnby 42ebefef92 Merge remote-tracking branch 'origin/refactor-usage-sn' into feat-migration 2024-06-19 19:32:30 +12:00
Jake Barnby 03d33601b0 Override find 2024-06-14 13:08:30 +12:00
118 changed files with 2260 additions and 19750 deletions
+10 -13
View File
@@ -18,11 +18,10 @@ _APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
_APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_COMPUTE_FORCE_HTTPS=disabled
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET=localhost
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
@@ -68,19 +67,17 @@ _APP_SMS_FROM=+123456789
_APP_SMS_PROJECTS_DENY_LIST=
_APP_STORAGE_LIMIT=30000000
_APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_COMPUTE_SIZE_LIMIT=30000000
_APP_COMPUTE_TIMEOUT=900
_APP_COMPUTE_BUILD_TIMEOUT=900
_APP_COMPUTE_CPUS=8
_APP_COMPUTE_MEMORY=8192
_APP_COMPUTE_INACTIVE_THRESHOLD=600
_APP_COMPUTE_MAINTENANCE_INTERVAL=600
_APP_COMPUTE_RUNTIMES_NETWORK=runtimes
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CPUS=8
_APP_FUNCTIONS_MEMORY=8192
_APP_FUNCTIONS_INACTIVE_THRESHOLD=600
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=600
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://exc1/v1
_APP_EXECUTOR_HOST=http://proxy/v1
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.24
_APP_SITES_FRAMEWORKS=sveltekit,nextjs,nuxt,astro,remix,static,flutter # TODO: Angular
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_DELAY=
_APP_MAINTENANCE_RETENTION_CACHE=2592000
+26 -9
View File
@@ -66,9 +66,6 @@ jobs:
docker compose up -d
sleep 10
- name: Logs
run: docker compose logs appwrite
- name: Doctor
run: docker compose exec -T appwrite doctor
@@ -130,6 +127,11 @@ jobs:
Messaging,
Migrations
]
tables-mode: [
'Project',
'Shared V1',
'Shared V2',
]
steps:
- name: checkout
@@ -146,13 +148,28 @@ jobs:
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 60
sleep 30
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
- name: Run ${{matrix.service}} Shared Tables Tests
run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
- name: Run ${{ matrix.service }} tests with ${{ matrix.tables-mode }} table mode
run: |
if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then
echo "Using shared tables V1"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=database_db_main
elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then
echo "Using shared tables V2"
export _APP_DATABASE_SHARED_TABLES=database_db_main
export _APP_DATABASE_SHARED_TABLES_V1=
else
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
export _APP_DATABASE_SHARED_TABLES_V1=
fi
docker compose exec -T \
-e _APP_DATABASE_SHARED_TABLES \
-e _APP_DATABASE_SHARED_TABLES_V1 \
appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug
benchmarking:
name: Benchmark
+4 -3
View File
@@ -114,8 +114,9 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -136,10 +137,10 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
->getResource();
$database = new Database($dbAdapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
+8 -421
View File
@@ -3074,7 +3074,7 @@ $projectCollections = array_merge([
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'default' => APP_FUNCTION_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
@@ -3163,364 +3163,6 @@ $projectCollections = array_merge([
],
],
'sites' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('sites'),
'name' => 'Sites',
'attributes' => [
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('enabled'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'array' => false,
],
[
'$id' => ID::custom('live'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'array' => false,
],
[
'$id' => ID::custom('installationId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('installationInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerRepositoryId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('repositoryId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('repositoryInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerBranch'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('providerRootDirectory'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('providerSilentMode'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => false,
'default' => false,
'array' => false,
],
// [
// '$id' => ID::custom('logging'),
// 'type' => Database::VAR_BOOLEAN,
// 'signed' => true,
// 'size' => 0,
// 'format' => '',
// 'filters' => [],
// 'required' => true,
// 'array' => false,
// ],
[
'$id' => ID::custom('framework'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('outputDirectory'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('installCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'$id' => ID::custom('fallbackFile'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('vars'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryVariables'],
],
[
'$id' => ID::custom('varsProject'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryProjectVariables'],
],
[
'$id' => ID::custom('timeout'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('specification'),
'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,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => true,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('adapter'), // ssr or static
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_search'),
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_name'),
'type' => Database::INDEX_KEY,
'attributes' => ['name'],
'lengths' => [256],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_enabled'),
'type' => Database::INDEX_KEY,
'attributes' => ['enabled'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_installationId'),
'type' => Database::INDEX_KEY,
'attributes' => ['installationId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_installationInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['installationInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_providerRepositoryId'),
'type' => Database::INDEX_KEY,
'attributes' => ['providerRepositoryId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_repositoryId'),
'type' => Database::INDEX_KEY,
'attributes' => ['repositoryId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_repositoryInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['repositoryInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_framework'),
'type' => Database::INDEX_KEY,
'attributes' => ['framework'],
'lengths' => [64],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_deploymentId'),
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentId'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
]
],
],
'deployments' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('deployments'),
@@ -3603,39 +3245,6 @@ $projectCollections = array_merge([
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('installCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('outputDirectory'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'$id' => ID::custom('path'),
'type' => Database::VAR_STRING,
@@ -4053,9 +3662,9 @@ $projectCollections = array_merge([
'size' => 1000000,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => ['json'],
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('sourceType'),
@@ -4097,7 +3706,7 @@ $projectCollections = array_merge([
'name' => 'Executions',
'attributes' => [
[
'$id' => ID::custom('resourceInternalId'),
'$id' => ID::custom('functionInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -4108,18 +3717,7 @@ $projectCollections = array_merge([
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'$id' => ID::custom('functionId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -4308,9 +3906,9 @@ $projectCollections = array_merge([
],
'indexes' => [
[
'$id' => ID::custom('_key_resource'),
'$id' => ID::custom('_key_function'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceId'],
'attributes' => ['functionId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
@@ -4433,17 +4031,6 @@ $projectCollections = array_merge([
'array' => false,
'filters' => ['encrypt']
],
[
'$id' => ID::custom('secret'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => false,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('search'),
'type' => Database::VAR_STRING,
+1 -25
View File
@@ -540,23 +540,6 @@ return [
'code' => 404,
],
/** Sites */
Exception::SITE_NOT_FOUND => [
'name' => Exception::SITE_NOT_FOUND,
'description' => 'Site with the requested ID could not be found.',
'code' => 404,
],
Exception::SITE_FRAMEWORK_UNSUPPORTED => [
'name' => Exception::SITE_FRAMEWORK_UNSUPPORTED,
'description' => 'The requested framework is either inactive or unsupported. Please check the value of the _APP_SITES_FRAMEWORKS environment variable.',
'code' => 404,
],
Exception::SITE_TEMPLATE_NOT_FOUND => [
'name' => Exception::SITE_TEMPLATE_NOT_FOUND,
'description' => 'Site Template with the requested ID could not be found.',
'code' => 404,
],
/** Builds */
Exception::BUILD_NOT_FOUND => [
'name' => Exception::BUILD_NOT_FOUND,
@@ -599,13 +582,6 @@ return [
'code' => 400,
],
/** Logs */
Exception::LOG_NOT_FOUND => [
'name' => Exception::LOG_NOT_FOUND,
'description' => 'Log with the requested ID could not be found.',
'code' => 404,
],
/** Databases */
Exception::DATABASE_NOT_FOUND => [
'name' => Exception::DATABASE_NOT_FOUND,
@@ -710,7 +686,7 @@ return [
],
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED,
'description' => 'The maximum number of attributes has been reached.',
'description' => 'The maximum number or size of attributes for this collection has been reached.',
'code' => 400,
],
Exception::ATTRIBUTE_VALUE_INVALID => [
-28
View File
@@ -217,34 +217,6 @@ return [
],
]
],
'sites' => [
'$model' => Response::MODEL_SITE,
'$resource' => true,
'$description' => 'This event triggers on any sites event.',
'deployments' => [
'$model' => Response::MODEL_DEPLOYMENT,
'$resource' => true,
'$description' => 'This event triggers on any deployments event.',
'create' => [
'$description' => 'This event triggers when a deployment is created.',
],
'delete' => [
'$description' => 'This event triggers when a deployment is deleted.'
],
'update' => [
'$description' => 'This event triggers when a deployment is updated.'
],
],
'create' => [
'$description' => 'This event triggers when a site is created.'
],
'delete' => [
'$description' => 'This event triggers when a site is deleted.',
],
'update' => [
'$description' => 'This event triggers when a site is updated.',
]
],
'functions' => [
'$model' => Response::MODEL_FUNCTION,
'$resource' => true,
-173
View File
@@ -1,173 +0,0 @@
<?php
/**
* List of Appwrite Sites supported frameworks
*/
// TODO: @Meldiron Angular
use Utopia\Config\Config;
$templateRuntimes = Config::getParam('template-runtimes');
function getVersions(array $versions, string $prefix)
{
return array_map(function ($version) use ($prefix) {
return $prefix . '-' . $version;
}, $versions);
}
return [
'nextjs' => [
'key' => 'nextjs',
'name' => 'Next.js',
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'ssr' => [
'key' => 'ssr',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './.next',
'startCommand' => 'sh helpers/next-js/server.sh',
'bundleCommand' => 'sh /usr/local/server/helpers/next-js/bundle.sh',
],
'static' => [
'key' => 'static',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './out',
'startCommand' => 'sh helpers/server.sh',
'bundleCommand' => '',
]
]
],
'nuxt' => [
'key' => 'nuxt',
'name' => 'Nuxt',
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'ssr' => [
'key' => 'ssr',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './.output',
'startCommand' => 'sh helpers/nuxt/server.sh',
'bundleCommand' => 'sh /usr/local/server/helpers/nuxt/bundle.sh',
],
'static' => [
'key' => 'static',
'buildCommand' => 'npm run generate',
'installCommand' => 'npm install',
'outputDirectory' => './dist',
'startCommand' => 'sh helpers/server.sh',
'bundleCommand' => '',
]
]
],
'sveltekit' => [
'key' => 'sveltekit',
'name' => 'SvelteKit',
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'ssr' => [
'key' => 'ssr',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './build',
'startCommand' => 'sh helpers/sveltekit/server.sh',
'bundleCommand' => 'sh /usr/local/server/helpers/sveltekit/bundle.sh',
],
'static' => [
'key' => 'static',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './build',
'startCommand' => 'sh helpers/server.sh',
'bundleCommand' => '',
]
]
],
'astro' => [
'key' => 'astro',
'name' => 'Astro',
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'ssr' => [
'key' => 'ssr',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './dist',
'startCommand' => 'sh helpers/astro/server.sh',
'bundleCommand' => 'sh /usr/local/server/helpers/astro/bundle.sh',
],
'static' => [
'key' => 'static',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './dist',
'startCommand' => 'sh helpers/server.sh',
'bundleCommand' => '',
]
]
],
'remix' => [
'key' => 'remix',
'name' => 'Remix',
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'ssr' => [
'key' => 'ssr',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './build',
'startCommand' => 'sh helpers/remix/server.sh',
'bundleCommand' => 'sh /usr/local/server/helpers/remix/bundle.sh',
],
'static' => [
'key' => 'static',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './build/client',
'startCommand' => 'sh helpers/server.sh',
'bundleCommand' => '',
]
]
],
'flutter' => [
'key' => 'flutter',
'name' => 'Flutter',
'buildRuntime' => 'flutter-3.24',
'runtimes' => getVersions($templateRuntimes['FLUTTER']['versions'], 'flutter'),
'adapters' => [
'static' => [
'key' => 'static',
'buildCommand' => 'flutter build web',
'installCommand' => '',
'outputDirectory' => './build/web',
'startCommand' => 'sh helpers/server.sh',
'bundleCommand' => '',
],
],
],
'other' => [
'key' => 'other',
'name' => 'Other',
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'static' => [
'key' => 'static',
'buildCommand' => '',
'installCommand' => '',
'outputDirectory' => './',
'startCommand' => 'sh helpers/server.sh',
'bundleCommand' => '',
],
]
],
];
+100 -69
View File
@@ -1,8 +1,39 @@
<?php
use Utopia\Config\Config;
$templateRuntimes = Config::getParam('template-runtimes');
const TEMPLATE_RUNTIMES = [
'NODE' => [
'name' => 'node',
'versions' => ['22', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5']
],
'PYTHON' => [
'name' => 'python',
'versions' => ['3.12', '3.11', '3.10', '3.9', '3.8']
],
'DART' => [
'name' => 'dart',
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
],
'GO' => [
'name' => 'go',
'versions' => ['1.23']
],
'PHP' => [
'name' => 'php',
'versions' => ['8.3', '8.2', '8.1', '8.0']
],
'DENO' => [
'name' => 'deno',
'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21']
],
'BUN' => [
'name' => 'bun',
'versions' => ['1.1', '1.0']
],
'RUBY' => [
'name' => 'ruby',
'versions' => ['3.3', '3.2', '3.1', '3.0']
],
];
function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = [])
{
@@ -31,24 +62,24 @@ return [
'timeout' => 15,
'useCases' => ['starter'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter'),
...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/starter'),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/starter'
),
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'),
...getRuntimes($templateRuntimes['GO'], '', 'main.go', 'go/starter'),
...getRuntimes(TEMPLATE_RUNTIMES['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'),
...getRuntimes(TEMPLATE_RUNTIMES['GO'], '', 'main.go', 'go/starter'),
...getRuntimes(
$templateRuntimes['PHP'],
TEMPLATE_RUNTIMES['PHP'],
'composer install',
'src/index.php',
'php/starter'
),
...getRuntimes($templateRuntimes['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'),
...getRuntimes($templateRuntimes['BUN'], 'bun install', 'src/main.ts', 'bun/starter'),
...getRuntimes($templateRuntimes['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'),
...getRuntimes(TEMPLATE_RUNTIMES['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'),
...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'),
...getRuntimes(TEMPLATE_RUNTIMES['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'),
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/starter">file</a>.',
'vcsProvider' => 'github',
@@ -70,7 +101,7 @@ return [
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/query-upstash-vector'
@@ -114,7 +145,7 @@ return [
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/query-redis-labs'
@@ -157,7 +188,7 @@ return [
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/query-neo4j-auradb'
@@ -209,7 +240,7 @@ return [
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/query-mongo-atlas'
@@ -246,7 +277,7 @@ return [
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/query-neon-postgres'
@@ -313,25 +344,25 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/prompt-chatgpt'
),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/prompt_chatgpt'
),
...getRuntimes(
$templateRuntimes['PHP'],
TEMPLATE_RUNTIMES['PHP'],
'composer install',
'src/index.php',
'php/prompt-chatgpt'
),
...getRuntimes(
$templateRuntimes['DART'],
TEMPLATE_RUNTIMES['DART'],
'dart pub get',
'lib/main.dart',
'dart/prompt_chatgpt'
@@ -374,19 +405,19 @@ return [
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/discord-command-bot'
),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt && python src/setup.py',
'src/main.py',
'python/discord_command_bot'
),
...getRuntimes(
$templateRuntimes['GO'],
TEMPLATE_RUNTIMES['GO'],
'',
'main.go',
'go/discord-command-bot'
@@ -437,7 +468,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/analyze-with-perspectiveapi'
@@ -473,19 +504,19 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/censor-with-redact'
),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/censor_with_redact'
),
...getRuntimes(
$templateRuntimes['DART'],
TEMPLATE_RUNTIMES['DART'],
'dart pub get',
'lib/main.dart',
'dart/censor_with_redact'
@@ -519,7 +550,7 @@ return [
'timeout' => 15,
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf')
...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf')
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-pdf">file</a>.',
'vcsProvider' => 'github',
@@ -542,7 +573,7 @@ return [
'useCases' => ['dev-tools'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/github-issue-bot'
@@ -585,7 +616,7 @@ return [
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/url-shortener'
@@ -636,19 +667,19 @@ return [
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/sync-with-algolia'
),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/sync_with_algolia'
),
...getRuntimes(
$templateRuntimes['PHP'],
TEMPLATE_RUNTIMES['PHP'],
'composer install',
'src/index.php',
'php/sync-with-algolia'
@@ -717,31 +748,31 @@ return [
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/sync-with-meilisearch'
),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/sync-with-meilisearch'
),
...getRuntimes(
$templateRuntimes['PHP'],
TEMPLATE_RUNTIMES['PHP'],
'composer install',
'src/index.php',
'php/sync-with-meilisearch'
),
...getRuntimes(
$templateRuntimes['BUN'],
TEMPLATE_RUNTIMES['BUN'],
'bun install',
'src/main.ts',
'bun/sync-with-meilisearch'
),
...getRuntimes(
$templateRuntimes['RUBY'],
TEMPLATE_RUNTIMES['RUBY'],
'bundle install',
'lib/main.rb',
'ruby/sync-with-meilisearch'
@@ -810,37 +841,37 @@ return [
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/whatsapp-with-vonage'
),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/whatsapp_with_vonage'
),
...getRuntimes(
$templateRuntimes['DART'],
TEMPLATE_RUNTIMES['DART'],
'dart pub get',
'lib/main.dart',
'dart/whatsapp-with-vonage'
),
...getRuntimes(
$templateRuntimes['PHP'],
TEMPLATE_RUNTIMES['PHP'],
'composer install',
'src/index.php',
'php/whatsapp-with-vonage'
),
...getRuntimes(
$templateRuntimes['BUN'],
TEMPLATE_RUNTIMES['BUN'],
'bun install',
'src/main.ts',
'bun/whatsapp-with-vonage'
),
...getRuntimes(
$templateRuntimes['RUBY'],
TEMPLATE_RUNTIMES['RUBY'],
'bundle install',
'lib/main.rb',
'ruby/whatsapp-with-vonage'
@@ -896,7 +927,7 @@ return [
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/push-notification-with-fcm'
@@ -952,19 +983,19 @@ return [
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/email-contact-form'
),
...getRuntimes(
$templateRuntimes['PYTHON'],
TEMPLATE_RUNTIMES['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/email_contact_form'
),
...getRuntimes(
$templateRuntimes['PHP'],
TEMPLATE_RUNTIMES['PHP'],
'composer install',
'src/index.php',
'php/email-contact-form'
@@ -1035,7 +1066,7 @@ return [
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/subscriptions-with-stripe'
@@ -1076,7 +1107,7 @@ return [
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/payments-with-stripe'
@@ -1133,7 +1164,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/text-generation-with-huggingface'
@@ -1167,7 +1198,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/language-translation-with-huggingface'
@@ -1201,7 +1232,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/image-classification-with-huggingface'
@@ -1259,7 +1290,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/object-detection-with-huggingface'
@@ -1317,7 +1348,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/speech-recognition-with-huggingface'
@@ -1375,7 +1406,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/text-to-speech-with-huggingface'
@@ -1433,7 +1464,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/generate-with-replicate'
@@ -1468,7 +1499,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/generate-with-together-ai'
@@ -1510,7 +1541,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/chat-with-perplexity-ai'
@@ -1550,7 +1581,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/generate-with-replicate'
@@ -1585,7 +1616,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/sync-with-pinecone'
@@ -1648,7 +1679,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/rag-with-langchain'
@@ -1711,7 +1742,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/speak-with-elevenlabs'
@@ -1766,7 +1797,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/speak-with-lmnt'
@@ -1807,7 +1838,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/chat-with-anyscale'
@@ -1848,7 +1879,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/music-generation-with-huggingface'
@@ -1890,7 +1921,7 @@ return [
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/generate-with-fal-ai'
@@ -1925,7 +1956,7 @@ return [
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/subscriptions-with-lemon-squeezy'
@@ -1980,7 +2011,7 @@ return [
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
TEMPLATE_RUNTIMES['NODE'],
'npm install',
'src/main.js',
'node/payments-with-lemon-squeezy'
-4
View File
@@ -58,10 +58,6 @@ $admins = [
'health.read',
'functions.read',
'functions.write',
'sites.read',
'sites.write',
'log.read',
'log.write',
'execution.read',
'execution.write',
'rules.read',
+3 -3
View File
@@ -5,17 +5,17 @@ use Appwrite\Functions\Specification;
return [
Specification::S_05VCPU_512MB => [
'slug' => Specification::S_05VCPU_512MB,
'memory' => 4096, // TODO: Revert this, it was just for QA server
'memory' => 512,
'cpus' => 1 // TODO: revert this, it's a temporary change to test function performance.
],
Specification::S_1VCPU_512MB => [
'slug' => Specification::S_1VCPU_512MB,
'memory' => 4096, // TODO: Revert this, it was just for QA server
'memory' => 512,
'cpus' => 1
],
Specification::S_1VCPU_1GB => [
'slug' => Specification::S_1VCPU_1GB,
'memory' => 4096, // TODO: Revert this, it was just for QA server
'memory' => 1024,
'cpus' => 1
],
Specification::S_2VCPU_2GB => [
-12
View File
@@ -64,18 +64,6 @@ return [ // List of publicly visible scopes
'functions.write' => [
'description' => 'Access to create, update, and delete your project\'s functions and code deployments',
],
'sites.read' => [
'description' => 'Access to read your project\'s sites and deployments',
],
'sites.write' => [
'description' => 'Access to create, update, and delete your project\'s sites and deployments',
],
'log.read' => [
'description' => 'Access to read your site\'s logs',
],
'log.write' => [
'description' => 'Access to update, and delete your site\'s logs',
],
'execution.read' => [
'description' => 'Access to read your project\'s execution logs',
],
-13
View File
@@ -173,19 +173,6 @@ return [
'optional' => false,
'icon' => '',
],
'sites' => [
'key' => 'sites',
'name' => 'Sites',
'subtitle' => 'The Sites Service allows you view, create and manage your web applications.',
'description' => '/docs/services/sites.md',
'controller' => 'api/sites.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/sites',
'tests' => false,
'optional' => true,
'icon' => '', // TODO: Update icon later
],
'functions' => [
'key' => 'functions',
'name' => 'Functions',
-181
View File
@@ -1,181 +0,0 @@
<?php
/**
* List of Appwrite Sites templates
*/
// TODO: @Meldiron Angular
const TEMPLATE_FRAMEWORKS = [
'SVELTEKIT' => [
'key' => 'sveltekit',
'name' => 'SvelteKit',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './build',
'buildRuntime' => 'node-22',
'adapter' => 'ssr',
'fallbackFile' => null,
],
'NEXTJS' => [
'key' => 'nextjs',
'name' => 'Next.js',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './.next',
'buildRuntime' => 'node-22',
'adapter' => 'ssr',
'fallbackFile' => null,
],
'NUXT' => [
'key' => 'nuxt',
'name' => 'Nuxt',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './.output',
'buildRuntime' => 'node-22',
'adapter' => 'ssr',
'fallbackFile' => null,
],
'REMIX' => [
'key' => 'remix',
'name' => 'Remix',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './build',
'buildRuntime' => 'node-22',
'adapter' => 'ssr',
'fallbackFile' => null,
],
'ASTRO' => [
'key' => 'astro',
'name' => 'Astro',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './dist',
'buildRuntime' => 'node-22',
'adapter' => 'ssr',
'fallbackFile' => null,
],
'FLUTTER' => [
'key' => 'flutter',
'name' => 'Flutter',
'installCommand' => '',
'buildCommand' => 'flutter build web',
'outputDirectory' => './build/web',
'buildRuntime' => 'flutter-3.24',
'adapter' => 'static',
'fallbackFile' => null,
],
];
function getFramework(string $frameworkEnum, array $overrides)
{
$settings = \array_merge(TEMPLATE_FRAMEWORKS[$frameworkEnum], $overrides);
return $settings;
}
return [
[
'key' => 'nextjs-starter',
'name' => 'Next.js starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://nextjs-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/nextjs-starter.png',
'frameworks' => [
getFramework('NEXTJS', [
'providerRootDirectory' => './nextjs/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'nuxt-starter',
'name' => 'Nuxt starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://nuxt-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/nuxt-starter.png',
'frameworks' => [
getFramework('NUXT', [
'providerRootDirectory' => './nuxt/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'sveltekit-starter',
'name' => 'SvelteKit starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://sveltekit-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/sveltekit-starter.png',
'frameworks' => [
getFramework('SVELTEKIT', [
'providerRootDirectory' => './sveltekit/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'astro-starter',
'name' => 'Astro starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://astro-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/astro-starter.png',
'frameworks' => [
getFramework('ASTRO', [
'providerRootDirectory' => './',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'astro-ssr-test-template',
'providerOwner' => 'Meldiron',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'remix-starter',
'name' => 'Remix starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://remix-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/remix-starter.png',
'frameworks' => [
getFramework('REMIX', [
'providerRootDirectory' => './remix/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
[
'key' => 'flutter-starter',
'name' => 'Flutter starter website',
'useCases' => ['starter'],
'demoUrl' => 'https://flutter-starter.sites.qa17.appwrite.org/',
'demoImage' => 'https://qa17.appwrite.org/console/images/sites/templates/flutter-starter.png',
'frameworks' => [
getFramework('FLUTTER', [
'providerRootDirectory' => './flutter/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
],
];
+11 -17
View File
@@ -4945,7 +4945,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 303,
"weight": 306,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5033,7 +5033,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 302,
"weight": 305,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5150,7 +5150,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 304,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5226,7 +5226,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 329,
"weight": 331,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5280,7 +5280,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 328,
"weight": 330,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5766,7 +5766,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5851,7 +5851,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 384,
"weight": 386,
"cookies": false,
"type": "",
"deprecated": false,
@@ -9360,16 +9360,11 @@
"any"
]
},
"resourceId": {
"functionId": {
"type": "string",
"description": "Resource ID.",
"description": "Function ID.",
"x-example": "5e5ea6g16897e"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "sites"
},
"trigger": {
"type": "string",
"description": "The trigger that caused the function to execute. Possible values can be: `http`, `schedule`, or `event`.",
@@ -9437,7 +9432,7 @@
},
"duration": {
"type": "number",
"description": "Resource(function\/site) execution duration in seconds.",
"description": "Function execution duration in seconds.",
"x-example": 0.4,
"format": "double"
},
@@ -9453,8 +9448,7 @@
"$createdAt",
"$updatedAt",
"$permissions",
"resourceId",
"resourceType",
"functionId",
"trigger",
"status",
"requestMethod",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11 -17
View File
@@ -5101,7 +5101,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 303,
"weight": 306,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5186,7 +5186,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 302,
"weight": 305,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5307,7 +5307,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 304,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5381,7 +5381,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 329,
"weight": 331,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5457,7 +5457,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 328,
"weight": 330,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5981,7 +5981,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6070,7 +6070,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 384,
"weight": 386,
"cookies": false,
"type": "",
"deprecated": false,
@@ -9539,16 +9539,11 @@
"any"
]
},
"resourceId": {
"functionId": {
"type": "string",
"description": "Resource ID.",
"description": "Function ID.",
"x-example": "5e5ea6g16897e"
},
"resourceType": {
"type": "string",
"description": "Resource type.",
"x-example": "sites"
},
"trigger": {
"type": "string",
"description": "The trigger that caused the function to execute. Possible values can be: `http`, `schedule`, or `event`.",
@@ -9618,7 +9613,7 @@
},
"duration": {
"type": "number",
"description": "Resource(function\/site) execution duration in seconds.",
"description": "Function execution duration in seconds.",
"x-example": 0.4,
"format": "double"
},
@@ -9634,8 +9629,7 @@
"$createdAt",
"$updatedAt",
"$permissions",
"resourceId",
"resourceType",
"functionId",
"trigger",
"status",
"requestMethod",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-40
View File
@@ -1,40 +0,0 @@
<?php
return [
'NODE' => [
'name' => 'node',
'versions' => ['22', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5']
],
'PYTHON' => [
'name' => 'python',
'versions' => ['3.12', '3.11', '3.10', '3.9', '3.8']
],
'DART' => [
'name' => 'dart',
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
],
'GO' => [
'name' => 'go',
'versions' => ['1.23']
],
'PHP' => [
'name' => 'php',
'versions' => ['8.3', '8.2', '8.1', '8.0']
],
'DENO' => [
'name' => 'deno',
'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21']
],
'BUN' => [
'name' => 'bun',
'versions' => ['1.1', '1.0']
],
'RUBY' => [
'name' => 'ruby',
'versions' => ['3.3', '3.2', '3.1', '3.0']
],
'FLUTTER' => [
'name' => 'flutter',
'versions' => ['3.24']
],
];
+2 -4
View File
@@ -46,13 +46,11 @@ App::get('/v1/console/variables')
$variables = new Document([
'_APP_DOMAIN_TARGET' => System::getEnv('_APP_DOMAIN_TARGET'),
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
'_APP_FUNCTIONS_SIZE_LIMIT' => +System::getEnv('_APP_FUNCTIONS_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_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS')
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled
]);
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
+6 -3
View File
@@ -152,7 +152,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
} catch (DuplicateException) {
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
} catch (LimitException) {
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED);
} catch (\Throwable $e) {
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
@@ -196,7 +196,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
} catch (LimitException) {
$dbForProject->deleteDocument('attributes', $attribute->getId());
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED);
} catch (\Throwable $e) {
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
@@ -392,6 +392,8 @@ function updateAttribute(
throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE);
} catch (NotFoundException) {
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
} catch (LimitException) {
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED);
}
}
@@ -2631,7 +2633,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
$validator = new IndexValidator(
$collection->getAttribute('attributes'),
$dbForProject->getAdapter()->getMaxIndexLength()
$dbForProject->getAdapter()->getMaxIndexLength(),
$dbForProject->getAdapter()->getInternalIndexesKeys(),
);
if (!$validator->isValid($index)) {
throw new Exception(Exception::INDEX_INVALID, $validator->getDescription());
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -13,7 +13,6 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -323,12 +322,11 @@ App::post('/v1/project/variables')
->label('sdk.response.model', Response::MODEL_VARIABLE)
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->action(function (string $key, string $value, bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
$variableId = ID::unique();
$variable = new Document([
@@ -343,7 +341,6 @@ App::post('/v1/project/variables')
'resourceType' => 'project',
'key' => $key,
'value' => $value,
'secret' => $secret,
'search' => implode(' ', [$variableId, $key, 'project']),
]);
+87 -40
View File
@@ -122,6 +122,10 @@ App::post('/v1/projects')
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
if ($projectId === 'console') {
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
$databases = Config::getParam('pools-database', []);
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
@@ -132,16 +136,14 @@ App::post('/v1/projects')
$dsn = $databases[array_rand($databases)];
}
if ($projectId === 'console') {
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
// TODO: Temporary until all projects are using shared tables.
if ($dsn === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn, $sharedTables)) {
$schema = 'appwrite';
$database = 'appwrite';
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
$dsn = $schema . '://' . System::getEnv('_APP_DATABASE_SHARED_TABLES', '') . '?database=' . $database;
$dsn = $schema . '://' . $dsn . '?database=' . $database;
if (!empty($namespace)) {
$dsn .= '&namespace=' . $namespace;
@@ -195,47 +197,92 @@ App::post('/v1/projects')
$adapter = $pools->get($dsn->getHost())->pop()->getResource();
$dbForProject = new Database($adapter, $cache);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$dbForProject
->setSharedTables(true)
->setTenant($project->getInternalId())
->setNamespace($dsn->getParam('namespace'));
} else {
$dbForProject
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
$sharedTables = $sharedTablesV1 || $sharedTablesV2;
$dbForProject->create();
$audit = new Audit($dbForProject);
$audit->setup();
$abuse = new TimeLimit('', 0, 1, $dbForProject);
$abuse->setup();
/** @var array $collections */
$collections = Config::getParam('collections', [])['projects'] ?? [];
foreach ($collections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
if (!$sharedTablesV2) {
if ($sharedTables) {
$dbForProject
->setSharedTables(true)
->setTenant($sharedTablesV1 ? $project->getInternalId() : null)
->setNamespace($dsn->getParam('namespace'));
} else {
$dbForProject
->setSharedTables(false)
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
$attributes = \array_map(function (array $attribute) {
return new Document($attribute);
}, $collection['attributes']);
$indexes = \array_map(function (array $index) {
return new Document($index);
}, $collection['indexes']);
$create = true;
try {
$dbForProject->createCollection($key, $attributes, $indexes);
$dbForProject->create();
} catch (Duplicate) {
// Collection already exists
$create = false;
}
if ($create || $projectTables) {
$audit = new Audit($dbForProject);
$audit->setup();
$abuse = new TimeLimit('', 0, 1, $dbForProject);
$abuse->setup();
}
if (!$create && $sharedTablesV1) {
$attributes = \array_map(fn ($attribute) => new Document($attribute), Audit::ATTRIBUTES);
$indexes = \array_map(fn (array $index) => new Document($index), Audit::INDEXES);
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom('audit'),
'$permissions' => [Permission::create(Role::any())],
'name' => 'audit',
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
$attributes = \array_map(fn ($attribute) => new Document($attribute), TimeLimit::ATTRIBUTES);
$indexes = \array_map(fn (array $index) => new Document($index), TimeLimit::INDEXES);
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom('abuse'),
'$permissions' => [Permission::create(Role::any())],
'name' => 'abuse',
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
}
if ($create || $sharedTablesV1) {
/** @var array $collections */
$collections = Config::getParam('collections', [])['projects'] ?? [];
foreach ($collections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
try {
$dbForProject->createCollection($key, $attributes, $indexes);
} catch (Duplicate) {
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom($key),
'$permissions' => [Permission::create(Role::any())],
'name' => $key,
'attributes' => $attributes,
'indexes' => $indexes,
'documentSecurity' => true
]));
}
}
}
}
+14 -66
View File
@@ -36,7 +36,7 @@ App::post('/v1/proxy/rules')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROXY_RULE)
->param('domain', null, new ValidatorDomain(), 'Domain name.')
->param('resourceType', null, new WhiteList(['api', 'function', 'site']), 'Action definition for the rule. Possible values are "api", "function" and "site"')
->param('resourceType', null, new WhiteList(['api', 'function']), 'Action definition for the rule. Possible values are "api", "function"')
->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.', true)
->inject('response')
->inject('project')
@@ -50,15 +50,9 @@ App::post('/v1/proxy/rules')
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.');
}
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (
($functionsDomain !== '' && str_ends_with($domain, $functionsDomain)) ||
($sitesDomain !== '' && str_ends_with($domain, $sitesDomain))
) {
// TODO: Refactor later
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions or sites domain or their subdomains to a specific resource. Please use a different domain.');
if ($functionsDomain != '' && str_ends_with($domain, $functionsDomain)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions domain or it\'s subdomain to specific resource. Please use different domain.');
}
if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) {
@@ -87,33 +81,18 @@ App::post('/v1/proxy/rules')
$resourceInternalId = '';
switch ($resourceType) {
case 'function':
if (empty($resourceId)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if ($resourceType == 'function') {
if (empty($resourceId)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$function = $dbForProject->getDocument('functions', $resourceId);
$function = $dbForProject->getDocument('functions', $resourceId);
if ($function->isEmpty()) {
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
if ($function->isEmpty()) {
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
$resourceInternalId = $function->getInternalId();
break;
case 'site':
if (empty($resourceId)) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$site = $dbForProject->getDocument('sites', $resourceId);
if ($site->isEmpty()) {
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
$resourceInternalId = $site->getInternalId();
break;
$resourceInternalId = $function->getInternalId();
}
try {
@@ -135,8 +114,8 @@ App::post('/v1/proxy/rules')
]);
$status = 'created';
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS');
if (!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) {
$status = 'verified';
}
@@ -369,34 +348,3 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
});
App::get('/v1/proxy/subdomains')
->desc('Check if subdomain is available')
->groups(['api', 'proxy'])
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'checkSubdomain')
->label('sdk.description', '/docs/references/proxy/check-subdomain.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_NONE)
->param('resourceType', null, new WhiteList(['function', 'site']), 'Action definition for the rule. Possible values are "function" and "site"')
->param('subdomain', '', new Text(256), 'Subdomain name.')
->inject('response')
->inject('dbForConsole')
->action(function (string $resourceType, string $subdomain, Response $response, Database $dbForConsole) {
//TODO: Add tests for this endpoint
$resourceDomain = $resourceType === 'site' ? System::getEnv('_APP_DOMAIN_SITES', '') : System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$domain = $subdomain . '.' . $resourceDomain;
$document = $dbForConsole->findOne('rules', [
Query::equal('domain', [$domain]),
]);
if ($document && !$document->isEmpty()) {
throw new Exception(Exception::RULE_ALREADY_EXISTS, 'Subdomain already assigned to different project.');
}
$response->noContent();
});
+6 -5
View File
@@ -68,19 +68,19 @@ App::post('/v1/storage/buckets')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions);
$compression ??= Compression::NONE;
try {
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
if (empty($files)) {
@@ -254,13 +254,13 @@ App::put('/v1/storage/buckets/:bucketId')
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
@@ -273,6 +273,7 @@ App::put('/v1/storage/buckets/:bucketId')
$enabled ??= $bucket->getAttribute('enabled', true);
$encryption ??= $bucket->getAttribute('encryption', true);
$antivirus ??= $bucket->getAttribute('antivirus', true);
$compression ??= $bucket->getAttribute('compression', Compression::NONE);
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions);
+58 -43
View File
@@ -1060,7 +1060,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
}
if ($user->isEmpty()) {
$hasSession = !$user->isEmpty();
if (!$hasSession) {
$user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user
}
@@ -1079,39 +1080,64 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
// Log user in
// Create session for the user if not logged in
if (!$hasSession) {
Authorization::setRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
Permission::delete(Role::user($user->getId())),
],
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = Auth::tokenGenerator();
$session = new Document(array_merge([
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
$session = $dbForProject->createDocument('sessions', $session);
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$permissions', [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
Permission::delete(Role::user($user->getId())),
]));
Authorization::setRole(Role::user($userId)->toString());
$dbForProject->purgeCachedDocument('users', $user->getId());
if (!Config::getParam('domainVerification')) {
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
}
Authorization::setRole(Role::user($userId)->toString());
$response
->addCookie(
name: Auth::$cookieName . '_legacy',
value: Auth::encodeSession($user->getId(), $secret),
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
secure: ('https' === $protocol),
httponly: true
)
->addCookie(
name: Auth::$cookieName,
value: Auth::encodeSession($user->getId(), $secret),
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
secure: ('https' === $protocol),
httponly: true,
sameSite: Config::getParam('cookieSamesite')
)
;
}
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
@@ -1125,22 +1151,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setParam('membershipId', $membership->getId())
;
if (!Config::getParam('domainVerification')) {
$response
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
$response
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
;
$response->dynamic(
$membership
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email')),
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email')),
Response::MODEL_MEMBERSHIP
);
});
+36 -64
View File
@@ -44,30 +44,29 @@ use function Swoole\Coroutine\batch;
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request) {
$errors = [];
foreach ($repositories as $repository) {
foreach ($repositories as $resource) {
try {
$resourceType = $repository->getAttribute('resourceType');
$resourceType = $resource->getAttribute('resourceType');
if ($resourceType !== "function" && $resourceType !== "site") {
if ($resourceType !== "function") {
continue;
}
$projectId = $repository->getAttribute('projectId');
$projectId = $resource->getAttribute('projectId');
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
$resourceCollection = $resourceType === "function" ? 'functions' : 'sites';
$resourceId = $repository->getAttribute('resourceId');
$resource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
$resourceInternalId = $resource->getInternalId();
$functionId = $resource->getAttribute('resourceId');
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
$functionInternalId = $function->getInternalId();
$deploymentId = ID::unique();
$repositoryId = $repository->getId();
$repositoryInternalId = $repository->getInternalId();
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
$installationId = $repository->getAttribute('installationId');
$installationInternalId = $repository->getAttribute('installationInternalId');
$productionBranch = $resource->getAttribute('providerBranch');
$repositoryId = $resource->getId();
$repositoryInternalId = $resource->getInternalId();
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
$installationId = $resource->getAttribute('installationId');
$installationInternalId = $resource->getAttribute('installationInternalId');
$productionBranch = $function->getAttribute('providerBranch');
$activate = false;
if ($providerBranch == $productionBranch && $external === false) {
@@ -91,7 +90,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$isAuthorized = !$external;
if (!$isAuthorized && !empty($providerPullRequestId)) {
if (\in_array($providerPullRequestId, $repository->getAttribute('providerPullRequestIds', []))) {
if (\in_array($providerPullRequestId, $resource->getAttribute('providerPullRequestIds', []))) {
$isAuthorized = true;
}
}
@@ -104,7 +103,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = '';
if (!empty($providerPullRequestId) && $resource->getAttribute('providerSilentMode', false) === false) {
if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) {
$latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerPullRequestId', [$providerPullRequestId]),
@@ -113,15 +112,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if (!$latestComment->isEmpty()) {
$latestCommentId = $latestComment->getAttribute('providerCommentId', '');
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
$comment->addBuild($project, $function, $commentStatus, $deploymentId, $action);
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} else {
$comment = new Comment();
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
$comment->addBuild($project, $function, $commentStatus, $deploymentId, $action);
$latestCommentId = \strval($github->createComment($owner, $repositoryName, $providerPullRequestId, $comment->generateComment()));
if (!empty($latestCommentId)) {
@@ -158,19 +156,19 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = $comment->getAttribute('providerCommentId', '');
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
$comment->addBuild($project, $function, $commentStatus, $deploymentId, $action);
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
}
}
if (!$isAuthorized) {
$resourceName = $resource->getAttribute('name');
$functionName = $function->getAttribute('name');
$projectName = $project->getAttribute('name');
$name = "{$resourceName} ({$projectName})";
$name = "{$functionName} ({$projectName})";
$message = 'Authorization required for external contributor.';
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
@@ -197,14 +195,11 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $resourceId,
'resourceInternalId' => $resourceInternalId,
'resourceType' => $resourceCollection,
'entrypoint' => $resource->getAttribute('entrypoint', ''),
'commands' => $resource->getAttribute('commands', ''),
'installCommand' => $resource->getAttribute('installCommand', ''),
'buildCommand' => $resource->getAttribute('buildCommand', ''),
'outputDirectory' => $resource->getAttribute('outputDirectory', ''),
'resourceId' => $functionId,
'resourceInternalId' => $functionInternalId,
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint'),
'commands' => $function->getAttribute('commands'),
'type' => 'vcs',
'installationId' => $installationId,
'installationInternalId' => $installationInternalId,
@@ -222,40 +217,17 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'providerCommitUrl' => $providerCommitUrl,
'providerCommentId' => \strval($latestCommentId),
'providerBranch' => $providerBranch,
'search' => implode(' ', [$deploymentId, $resource->getAttribute('entrypoint', '')]),
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
'activate' => $activate,
]));
// Preview deployments for sites
if ($resource->getCollection() === 'sites') {
$projectId = $project->getId();
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
$ruleId = md5($domain);
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'deployment',
'resourceId' => $deploymentId,
'resourceInternalId' => $deployment->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
}
if (!empty($providerCommitHash) && $resource->getAttribute('providerSilentMode', false) === false) {
$resourceName = $resource->getAttribute('name');
if (!empty($providerCommitHash) && $function->getAttribute('providerSilentMode', false) === false) {
$functionName = $function->getAttribute('name');
$projectName = $project->getAttribute('name');
$name = "{$resourceName} ({$projectName})";
$name = "{$functionName} ({$projectName})";
$message = 'Starting...';
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
$providerRepositoryId = $resource->getAttribute('providerRepositoryId');
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
@@ -266,17 +238,17 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$owner = $github->getOwnerName($providerInstallationId);
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/$resourceCollection/$resourceType-$resourceId";
$providerTargetUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/functions/function-$functionId";
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, 'pending', $message, $providerTargetUrl, $name);
}
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($resource)
->setResource($function)
->setDeployment($deployment)
->setProject($project); // set the project because it won't be set for git deployments
$queueForBuilds->trigger(); // must trigger here so that we create a build for each function/site
$queueForBuilds->trigger(); // must trigger here so that we create a build for each function
//TODO: Add event?
} catch (Throwable $e) {
@@ -962,7 +934,7 @@ App::post('/v1/vcs/github/events')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
//find resourceId from relevant resources table
//find functionId from functions table
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::limit(100),
@@ -974,7 +946,7 @@ App::post('/v1/vcs/github/events')
}
} elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") {
// TODO: Use worker for this job instead (update function/site as well)
// TODO: Use worker for this job instead (update function as well)
$providerInstallationId = $parsedPayload["installationId"];
$installations = $dbForConsole->find('installations', [
+82 -195
View File
@@ -10,7 +10,6 @@ use Appwrite\Event\Func;
use Appwrite\Event\Usage;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Network\Validator\Origin;
use Appwrite\Platform\Appwrite;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
@@ -38,7 +37,6 @@ use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
use Utopia\System\System;
use Utopia\Validator\Hostname;
use Utopia\Validator\Text;
@@ -53,31 +51,32 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$host = $request->getHostname() ?? '';
$rule = Authorization::skip(fn () => $dbForConsole->getDocument('rules', md5($host)));
$route = Authorization::skip(fn () => $dbForConsole->getDocument('rules', md5($host)));
if ($rule->isEmpty()) {
if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '') || $host === System::getEnv('_APP_DOMAIN_SITES', '')) {
if ($route->isEmpty()) {
if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.');
}
if (\str_ends_with($host, System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) || \str_ends_with($host, System::getEnv('_APP_DOMAIN_SITES', ''))) {
if (\str_ends_with($host, System::getEnv('_APP_DOMAIN_FUNCTIONS', ''))) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain is not connected to any Appwrite resource yet. Please configure custom domain or function domain to allow this request.');
}
if (System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled') {
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL && $host !== System::getEnv('_APP_CONSOLE_DOMAIN', '')) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.');
}
}
// Act as API - no Proxy logic
$utopia->getRoute()?->label('error', '');
return false;
}
$projectId = $rule->getAttribute('projectId');
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$projectId = $route->getAttribute('projectId');
$project = Authorization::skip(
fn () => $dbForConsole->getDocument('projects', $projectId)
);
if (array_key_exists('proxy', $project->getAttribute('services', []))) {
$status = $project->getAttribute('services', [])['proxy'];
if (!$status) {
@@ -91,29 +90,24 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
return false;
}
$type = $rule->getAttribute('resourceType');
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
$resourceCollection = match($type) {
'function' => 'functions',
'site' => 'sites',
'deployment' => 'deployments',
};
$type = $route->getAttribute('resourceType');
if ($type === 'function') {
$utopia->getRoute()?->label('sdk.namespace', 'functions');
$utopia->getRoute()?->label('sdk.method', 'createExecution');
if (System::getEnv('_APP_OPTIONS_COMPUTE_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if (System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
}
}
$resourceId = $rule->getAttribute('resourceId');
$projectId = $rule->getAttribute('projectId');
$functionId = $route->getAttribute('resourceId');
$projectId = $route->getAttribute('projectId');
$path = ($swooleRequest->server['request_uri'] ?? '/');
$query = ($swooleRequest->server['query_string'] ?? '');
@@ -121,6 +115,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$path .= '?' . $query;
}
$body = $swooleRequest->getContent() ?? '';
$method = $swooleRequest->server['request_method'];
@@ -128,57 +123,31 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
/** @var Database $dbForProject */
$dbForProject = $getProjectDB($project);
if ($resourceCollection === 'deployments') {
$subResource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
$resource = Authorization::skip(fn () => $dbForProject->getDocument($subResource->getAttribute('resourceType'), $subResource->getAttribute('resourceId')));
} else {
$resource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
}
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
if ($resource->isEmpty() || !$resource->getAttribute('enabled')) {
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND);
}
if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $resourceId)) {
if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $functionId)) {
throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_BLOCKED);
}
$version = match($type) {
'function' => $resource->getAttribute('version', 'v2'),
'site' => 'v4',
'deployment' => 'v4'
};
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$spec = Config::getParam('runtime-specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)];
$runtime = match($type) {
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
'site' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
'deployment' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
default => null
};
if ($resource->getAttribute('adapter', '') === 'static') {
$runtime = $runtimes['static-1'] ?? null;
}
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
if (\is_null($runtime)) {
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
$deploymentId = match($type) {
'function' => $resource->getAttribute('deployment', ''),
'site' => $resource->getAttribute('deploymentId', ''),
'deployment' => $subResource->getId()
};
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $deploymentId));
if ($deployment->getAttribute('resourceId') !== $resource->getId()) {
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
}
@@ -196,34 +165,28 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
throw new AppwriteException(AppwriteException::BUILD_NOT_READY);
}
//todo: figure out for sites/functions
if ($type === 'function') {
$permissions = $resource->getAttribute('execute');
$permissions = $function->getAttribute('execute');
if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
}
if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
}
$jwtExpiry = $function->getAttribute('timeout', 900);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
$apiKey = $jwtObj->encode([
'projectId' => $project->getId(),
'scopes' => $function->getAttribute('scopes', [])
]);
$headers = \array_merge([], $requestHeaders);
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = '';
$headers['x-appwrite-user-jwt'] = '';
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
$headers['x-appwrite-continent-eu'] = 'false';
//todo: check if this would work for sites
if ($type === 'function') {
$jwtExpiry = $resource->getAttribute('timeout', 900);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
$apiKey = $jwtObj->encode([
'projectId' => $project->getId(),
'scopes' => $resource->getAttribute('scopes', [])
]);
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-jwt'] = '';
}
$ip = $headers['x-real-ip'] ?? '';
if (!empty($ip)) {
$record = $geodb->get($ip);
@@ -249,10 +212,12 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$execution = new Document([
'$id' => $executionId,
'$permissions' => [],
'resourceInternalId' => $resource->getInternalId(),
'resourceId' => $resource->getId(),
'functionInternalId' => $function->getInternalId(),
'functionId' => $function->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'trigger' => 'http', // http / schedule / event
'status' => 'processing', // waiting / processing / completed / failed
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
@@ -261,21 +226,13 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
'errors' => '',
'logs' => '',
'duration' => 0.0,
'search' => implode(' ', [$resourceId, $executionId]),
'search' => implode(' ', [$functionId, $executionId]),
]);
if ($type === 'function') {
$execution->setAttribute('resourceType', 'functions');
$execution->setAttribute('trigger', 'http'); // http / schedule / event
$execution->setAttribute('status', 'processing'); // waiting / processing / completed / failed
} elseif ($type === 'site') {
$execution->setAttribute('resourceType', 'sites');
}
$queueForEvents
->setParam('functionId', $resource->getId())
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->setContext('function', $resource);
->setContext('function', $function);
$durationStart = \microtime(true);
@@ -292,12 +249,12 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
}
// Shared vars
foreach ($resource->getAttribute('varsProject', []) as $var) {
foreach ($function->getAttribute('varsProject', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
// Function vars
foreach ($resource->getAttribute('vars', []) as $var) {
foreach ($function->getAttribute('vars', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
@@ -308,14 +265,14 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
// Appwrite vars
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $resourceId,
'APPWRITE_FUNCTION_NAME' => $resource->getAttribute('name'),
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_COMPUTE_CPUS' => $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
'APPWRITE_COMPUTE_MEMORY' => $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
'APPWRITE_FUNCTION_CPUS' => $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
'APPWRITE_FUNCTION_MEMORY' => $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
'APPWRITE_VERSION' => APP_VERSION_STABLE,
'APPWRITE_REGION' => $project->getAttribute('region'),
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
@@ -336,61 +293,26 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
/** Execute function */
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
try {
$version = match($type) {
'function' => $resource->getAttribute('version', 'v2'),
'site' => 'v4',
'deployment' => 'v4'
};
$entrypoint = match($type) {
'function' => $deployment->getAttribute('entrypoint', ''),
'site' => '',
'deployment' => ''
};
if ($type === 'function') {
$runtimeEntrypoint = match ($version) {
'v2' => '',
default => 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $runtime['startCommand'] . '"'
};
} elseif ($type === 'site' || $type === 'deployment') {
$frameworks = Config::getParam('frameworks', []);
$framework = $frameworks[$resource->getAttribute('framework', '')] ?? null;
$startCommand = $runtime['startCommand'];
if (!is_null($framework)) {
$adapter = ($framework['adapters'] ?? [])[$resource->getAttribute('adapter', '')] ?? null;
if (!is_null($adapter) && isset($adapter['startCommand'])) {
$startCommand = $adapter['startCommand'];
}
}
$runtimeEntrypoint = 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $startCommand . '"';
}
$entrypoint = match($type) {
'function' => $deployment->getAttribute('entrypoint', ''),
'site' => '',
'deployment' => ''
};
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
$command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"';
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $resource->getAttribute('timeout', 30),
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $build->getAttribute('path', ''),
entrypoint: $entrypoint,
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $runtimeEntrypoint,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $resource->getAttribute('logging', true),
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
@@ -403,26 +325,21 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
if ($type === 'function') {
$execution->setAttribute('status', $status);
}
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('status', $status);
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
$durationEnd = \microtime(true);
$execution
->setAttribute('duration', $durationEnd - $durationStart)
->setAttribute('responseStatusCode', 500);
if ($type === 'function') {
$execution
->setAttribute('status', 'failed')
->setAttribute('responseStatusCode', 500)
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
}
Console::error($th->getMessage());
if ($th instanceof AppwriteException) {
@@ -438,31 +355,22 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$queueForUsage
->addMetric(METRIC_NETWORK_REQUESTS, 1)
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize());
//todo: add metrics for sites
if ($type === 'function') {
$queueForUsage
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $resource->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $resource->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $resource->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)));
}
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->setProject($project)
->trigger()
;
$queueForUsage
$queueForFunctions
->setType(Func::TYPE_ASYNC_WRITE)
->setExecution($execution)
->setProject($project)
->trigger();
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);
}
}
$execution->setAttribute('logs', '');
@@ -484,11 +392,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$contentType = $header['value'];
}
if (\strtolower($header['name']) === 'transfer-encoding') {
continue;
}
$response->addHeader(\strtolower($header['name']), $header['value']);
$response->setHeader($header['name'], $header['value']);
}
$response
@@ -561,7 +465,6 @@ App::init()
// Only run Router when external domain
if ($host !== $mainDomain) {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
$utopia->getRoute()?->label('router', 'true');
return;
}
}
@@ -780,7 +683,6 @@ App::options()
// Only run Router when external domain
if ($host !== $mainDomain) {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
$utopia->getRoute()?->label('router', 'true');
return;
}
}
@@ -1075,9 +977,7 @@ App::get('/robots.txt')
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
$utopia->getRoute()?->label('router', 'true');
}
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked);
}
});
@@ -1104,9 +1004,7 @@ App::get('/humans.txt')
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) {
$utopia->getRoute()?->label('router', 'true');
}
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked);
}
});
@@ -1198,21 +1096,10 @@ App::get('/v1/ping')
App::wildcard()
->groups(['api'])
->label('scope', 'global')
->inject('utopia')
->action(function (App $utopia) {
$handeledByRouter = $utopia->getRoute()?->getLabel('router', 'false');
if (\boolval($handeledByRouter)) {
return;
}
->action(function () {
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
});
foreach (Config::getParam('services', []) as $service) {
include_once $service['controller'];
}
// Modules
$platform = new Appwrite();
$platform->init(Service::TYPE_HTTP);
-9
View File
@@ -163,15 +163,6 @@ $usageDatabaseListener = function (string $event, Document $document, Usage $que
$queueForUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'sites':
$queueForUsage
->addMetric(METRIC_SITES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForUsage
->addReduce($document);
+53 -58
View File
@@ -12,10 +12,12 @@ use Swoole\Process;
use Utopia\Abuse\Adapters\Database\TimeLimit;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -60,8 +62,6 @@ include __DIR__ . '/controllers/general.php';
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
$app = new App('UTC');
$app->setCompression(true);
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
go(function () use ($register, $app) {
$pools = $register->get('pools');
@@ -91,9 +91,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
Console::success('[Setup] - Server database init started...');
try {
Console::success('[Setup] - Creating database: appwrite...');
Console::success('[Setup] - Creating console database...');
$dbForConsole->create();
} catch (\Throwable $e) {
} catch (Duplicate) {
Console::success('[Setup] - Skip: metadata table already exists');
}
@@ -118,34 +118,10 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
continue;
}
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
Console::success('[Setup] - Creating console collection: ' . $collection['$id'] . '...');
$attributes = [];
$indexes = [];
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => ID::custom($attribute['$id']),
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
'default' => $attribute['default'] ?? null,
'format' => $attribute['format'] ?? ''
]);
}
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
$dbForConsole->createCollection($key, $attributes, $indexes);
}
@@ -180,36 +156,55 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
throw new Exception('Files collection is not configured.');
}
$attributes = [];
$indexes = [];
foreach ($files['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => ID::custom($attribute['$id']),
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
'default' => $attribute['default'] ?? null,
'format' => $attribute['format'] ?? ''
]);
}
foreach ($files['indexes'] as $index) {
$indexes[] = new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $files['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $files['indexes']);
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
}
$projectCollections = $collections['projects'];
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$sharedTablesV2 = \array_diff($sharedTables, $sharedTablesV1);
$cache = $app->getResource('cache');
foreach ($sharedTablesV2 as $hostname) {
$adapter = $pools
->get($hostname)
->pop()
->getResource();
$dbForProject = (new Database($adapter, $cache))
->setDatabase('appwrite')
->setSharedTables(true)
->setTenant(null)
->setNamespace(System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''));
try {
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
$dbForProject->create();
} catch (Duplicate) {
Console::success('[Setup] - Skip: metadata table already exists');
}
foreach ($projectCollections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
if (!$dbForProject->getCollection($key)->isEmpty()) {
continue;
}
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
Console::success('[Setup] - Creating project collection: ' . $collection['$id'] . '...');
$dbForProject->createCollection($key, $attributes, $indexes);
}
}
$pools->reclaim();
Console::success('[Setup] - Server database init completed...');
@@ -225,7 +220,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
});
});
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
App::setResource('swooleRequest', fn () => $swooleRequest);
App::setResource('swooleResponse', fn () => $swooleResponse);
+10 -38
View File
@@ -135,7 +135,6 @@ const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4
const APP_DATABASE_TIMEOUT_MILLISECONDS = 15_000;
const APP_DATABASE_QUERY_MAX_VALUES = 500;
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_SITES = '/storage/sites';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_BUILDS = '/storage/builds';
const APP_STORAGE_CACHE = '/storage/cache';
@@ -154,9 +153,9 @@ const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
const APP_HOSTNAME_INTERNAL = 'appwrite';
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
const APP_COMPUTE_MEMORY_DEFAULT = 512;
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_4VCPU_4GB;
const APP_FUNCTION_CPUS_DEFAULT = 4;
const APP_FUNCTION_MEMORY_DEFAULT = 4096;
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
@@ -182,7 +181,6 @@ const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_PROJECTS = 'projects';
const DELETE_TYPE_SITES = 'sites';
const DELETE_TYPE_FUNCTIONS = 'functions';
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
const DELETE_TYPE_USERS = 'users';
@@ -260,7 +258,6 @@ const METRIC_FILES = 'files';
const METRIC_FILES_STORAGE = 'files.storage';
const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files';
const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage';
const METRIC_SITES = 'sites';
const METRIC_FUNCTIONS = 'functions';
const METRIC_DEPLOYMENTS = 'deployments';
const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
@@ -279,29 +276,15 @@ const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS = '{functionInternalId}.builds.compute.success';
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED = '{functionInternalId}.builds.compute.failed';
const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
const METRIC_SITES_ID_BUILDS = 'sites.{siteInternalId}.builds';
const METRIC_SITES_ID_BUILDS_SUCCESS = 'sites.{siteInternalId}.builds.success';
const METRIC_SITES_ID_BUILDS_FAILED = 'sites.{siteInternalId}.builds.failed';
const METRIC_SITES_ID_BUILDS_STORAGE = 'sites.{siteInternalId}.builds.storage';
const METRIC_SITES_ID_BUILDS_COMPUTE = 'sites.{siteInternalId}.builds.compute';
const METRIC_SITES_ID_BUILDS_COMPUTE_SUCCESS = 'sites.{siteInternalId}.builds.compute.success';
const METRIC_SITES_ID_BUILDS_COMPUTE_FAILED = 'sites.{siteInternalId}.builds.compute.failed';
const METRIC_SITES_ID_BUILDS_MB_SECONDS = 'sites.{siteInternalId}.builds.mbSeconds';
const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
const METRIC_EXECUTIONS = 'executions';
const METRIC_EXECUTIONS_COMPUTE = 'executions.compute';
const METRIC_EXECUTIONS_MB_SECONDS = 'executions.mbSeconds';
const METRIC_FUNCTION_ID_EXECUTIONS = '{functionInternalId}.executions';
const METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE = '{functionInternalId}.executions.compute';
const METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS = '{functionInternalId}.executions.mbSeconds';
const METRIC_SITE_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
const METRIC_SITE_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
const METRIC_SITE_ID_BUILDS = '{siteInternalId}.builds';
const METRIC_SITE_ID_BUILDS_STORAGE = '{siteInternalId}.builds.storage';
const METRIC_SITE_ID_BUILDS_COMPUTE = '{siteInternalId}.builds.compute';
const METRIC_SITE_ID_BUILDS_MB_SECONDS = '{siteInternalId}.builds.mbSeconds';
const METRIC_NETWORK_REQUESTS = 'network.requests';
const METRIC_NETWORK_INBOUND = 'network.inbound';
const METRIC_NETWORK_OUTBOUND = 'network.outbound';
@@ -310,7 +293,6 @@ const METRIC_NETWORK_OUTBOUND = 'network.outbound';
const RESOURCE_TYPE_PROJECTS = 'projects';
const RESOURCE_TYPE_FUNCTIONS = 'functions';
const RESOURCE_TYPE_SITES = 'sites';
const RESOURCE_TYPE_DATABASES = 'databases';
const RESOURCE_TYPE_BUCKETS = 'buckets';
const RESOURCE_TYPE_PROVIDERS = 'providers';
@@ -331,7 +313,6 @@ if (!App::isProduction()) {
/*
* ENV vars
*/
Config::load('template-runtimes', __DIR__ . '/config/template-runtimes.php');
Config::load('events', __DIR__ . '/config/events.php');
Config::load('auth', __DIR__ . '/config/auth.php');
Config::load('apis', __DIR__ . '/config/apis.php'); // List of APIs
@@ -339,7 +320,6 @@ Config::load('errors', __DIR__ . '/config/errors.php');
Config::load('oAuthProviders', __DIR__ . '/config/oAuthProviders.php');
Config::load('platforms', __DIR__ . '/config/platforms.php');
Config::load('collections', __DIR__ . '/config/collections.php');
Config::load('frameworks', __DIR__ . '/config/frameworks.php');
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
Config::load('runtimes-v2', __DIR__ . '/config/runtimes-v2.php');
Config::load('usage', __DIR__ . '/config/usage.php');
@@ -365,7 +345,6 @@ Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
Config::load('runtime-specifications', __DIR__ . '/config/runtimes/specifications.php');
Config::load('function-templates', __DIR__ . '/config/function-templates.php');
Config::load('site-templates', __DIR__ . '/config/site-templates.php');
/**
* New DB Filters
@@ -588,7 +567,7 @@ Database::addFilter(
return $database
->find('variables', [
Query::equal('resourceInternalId', [$document->getInternalId()]),
Query::equal('resourceType', ['function', 'site']),
Query::equal('resourceType', ['function']),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}
@@ -1446,14 +1425,9 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -1508,7 +1482,9 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -1563,10 +1539,6 @@ App::setResource('deviceForFiles', function ($project) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForSites', function ($project) {
return getDevice(APP_STORAGE_SITES . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceForFunctions', function ($project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
+3 -1
View File
@@ -93,7 +93,9 @@ if (!function_exists('getProjectDB')) {
$database = new Database($adapter, getCache());
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
+20 -22
View File
@@ -86,7 +86,7 @@ $image = $this->getParam('image', '');
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_COMPUTE_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
@@ -132,14 +132,12 @@ $image = $this->getParam('image', '');
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_COMPUTE_SIZE_LIMIT
- _APP_COMPUTE_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
- _APP_COMPUTE_CPUS
- _APP_COMPUTE_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_RUNTIMES
- _APP_SITES_FRAMEWORKS
- _APP_SITES_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_LOGGING_CONFIG
@@ -402,13 +400,13 @@ $image = $this->getParam('image', '');
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_COMPUTE_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
- _APP_COMPUTE_CPUS
- _APP_COMPUTE_MEMORY
- _APP_COMPUTE_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_COMPUTE_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
@@ -492,10 +490,10 @@ $image = $this->getParam('image', '');
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_COMPUTE_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
- _APP_COMPUTE_CPUS
- _APP_COMPUTE_MEMORY
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
@@ -798,7 +796,7 @@ $image = $this->getParam('image', '');
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.7.0
image: openruntimes/executor:0.6.11
networks:
- appwrite
- runtimes
@@ -810,9 +808,9 @@ $image = $this->getParam('image', '');
# It's not possible to share mount file between 2 containers without host mount (copying is too slow)
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_COMPUTE_INACTIVE_THRESHOLD
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_COMPUTE_MAINTENANCE_INTERVAL
- OPR_EXECUTOR_NETWORK=$_APP_COMPUTE_RUNTIMES_NETWORK
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
- OPR_EXECUTOR_ENV=$_APP_ENV
+9 -7
View File
@@ -93,7 +93,9 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -126,7 +128,9 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -150,7 +154,9 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
$databases[$dsn->getHost()] = $database;
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -256,10 +262,6 @@ Server::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
Server::setResource('deviceForSites', function (Document $project) {
return getDevice(APP_STORAGE_SITES . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForFunctions', function (Document $project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
+6 -6
View File
@@ -45,16 +45,16 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.16.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "0.43.0",
"utopia-php/abuse": "0.43.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "0.43.0",
"utopia-php/audit": "0.43.*",
"utopia-php/cache": "0.11.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.53.16",
"utopia-php/database": "0.53.20",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "dev-fix-prevent-duplicate-compression as 0.33.99",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.2.*",
"utopia-php/image": "0.7.*",
"utopia-php/locale": "0.4.*",
@@ -99,8 +99,8 @@
"php": "8.3"
},
"allow-plugins": {
"php-http/discovery": true,
"tbachert/spi": true
"php-http/discovery": false,
"tbachert/spi": false
}
}
}
Generated
+127 -137
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "026d47ead39933ab29b2ea7046bfec4f",
"content-hash": "ae3b9a491c9870a4897cdf712caba1e3",
"packages": [
{
"name": "adhocore/jwt",
@@ -157,16 +157,16 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.16.5",
"version": "0.16.4",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "1e430646fdf847a7caf3c611dcf3d6d5a28c3fd9"
"reference": "7e4741337b9373f77210396e68eca539018cabd1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/1e430646fdf847a7caf3c611dcf3d6d5a28c3fd9",
"reference": "1e430646fdf847a7caf3c611dcf3d6d5a28c3fd9",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/7e4741337b9373f77210396e68eca539018cabd1",
"reference": "7e4741337b9373f77210396e68eca539018cabd1",
"shasum": ""
},
"require": {
@@ -206,9 +206,9 @@
],
"support": {
"issues": "https://github.com/appwrite/runtimes/issues",
"source": "https://github.com/appwrite/runtimes/tree/0.16.5"
"source": "https://github.com/appwrite/runtimes/tree/0.16.4"
},
"time": "2024-11-25T15:17:06+00:00"
"time": "2024-10-26T10:39:59+00:00"
},
{
"name": "beberlei/assert",
@@ -709,16 +709,16 @@
},
{
"name": "google/protobuf",
"version": "v4.29.0",
"version": "v4.28.3",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "0ef6b2eb74b782f3f9023276c324d22e440f7587"
"reference": "c5c311e0f3d89928251ac5a2f0e3db283612c100"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0ef6b2eb74b782f3f9023276c324d22e440f7587",
"reference": "0ef6b2eb74b782f3f9023276c324d22e440f7587",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c5c311e0f3d89928251ac5a2f0e3db283612c100",
"reference": "c5c311e0f3d89928251ac5a2f0e3db283612c100",
"shasum": ""
},
"require": {
@@ -747,9 +747,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.0"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.28.3"
},
"time": "2024-11-27T18:37:40+00:00"
"time": "2024-10-22T22:27:17+00:00"
},
{
"name": "jean85/pretty-package-versions",
@@ -2343,9 +2343,9 @@
"type": "library",
"extra": {
"branch-alias": {
"v8.3": "8.3.x-dev",
"v10.0": "10.0.x-dev",
"v9.0": "9.0.x-dev",
"v10.0": "10.0.x-dev"
"v8.3": "8.3.x-dev"
}
},
"autoload": {
@@ -2386,16 +2386,16 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.1",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": ""
},
"require": {
@@ -2433,7 +2433,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -2449,31 +2449,30 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/http-client",
"version": "v7.2.0",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "955e43336aff03df1e8a8e17daefabb0127a313b"
"reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/955e43336aff03df1e8a8e17daefabb0127a313b",
"reference": "955e43336aff03df1e8a8e17daefabb0127a313b",
"url": "https://api.github.com/repos/symfony/http-client/zipball/c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a",
"reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a",
"shasum": ""
},
"require": {
"php": ">=8.2",
"psr/log": "^1|^2|^3",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/http-client-contracts": "~3.4.3|^3.5.1",
"symfony/http-client-contracts": "^3.4.1",
"symfony/service-contracts": "^2.5|^3"
},
"conflict": {
"amphp/amp": "<2.5",
"php-http/discovery": "<1.15",
"symfony/http-foundation": "<6.4"
},
@@ -2484,14 +2483,14 @@
"symfony/http-client-implementation": "3.0"
},
"require-dev": {
"amphp/http-client": "^4.2.1|^5.0",
"amphp/http-tunnel": "^1.0|^2.0",
"amphp/amp": "^2.5",
"amphp/http-client": "^4.2.1",
"amphp/http-tunnel": "^1.0",
"amphp/socket": "^1.1",
"guzzlehttp/promises": "^1.4|^2.0",
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
"psr/http-client": "^1.0",
"symfony/amphp-http-client-meta": "^1.0|^2.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
@@ -2528,7 +2527,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.2.0"
"source": "https://github.com/symfony/http-client/tree/v7.1.8"
},
"funding": [
{
@@ -2544,20 +2543,20 @@
"type": "tidelift"
}
],
"time": "2024-11-29T08:22:02+00:00"
"time": "2024-11-13T13:40:27+00:00"
},
{
"name": "symfony/http-client-contracts",
"version": "v3.5.1",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client-contracts.git",
"reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9"
"reference": "20414d96f391677bf80078aa55baece78b82647d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9",
"reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9",
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d",
"reference": "20414d96f391677bf80078aa55baece78b82647d",
"shasum": ""
},
"require": {
@@ -2606,7 +2605,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1"
"source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -2622,7 +2621,7 @@
"type": "tidelift"
}
],
"time": "2024-11-25T12:02:18+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@@ -2862,16 +2861,16 @@
},
{
"name": "symfony/service-contracts",
"version": "v3.5.1",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
"reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
"shasum": ""
},
"require": {
@@ -2925,7 +2924,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
"source": "https://github.com/symfony/service-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -2941,7 +2940,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "tbachert/spi",
@@ -3136,16 +3135,16 @@
},
{
"name": "utopia-php/abuse",
"version": "0.43.0",
"version": "0.43.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
"reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6"
"reference": "e404c21e8dcf6a310bc83cf1d74e716b105598fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/6346a3b4c5177a43160035a7289e30fdfb0790d6",
"reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6",
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/e404c21e8dcf6a310bc83cf1d74e716b105598fa",
"reference": "e404c21e8dcf6a310bc83cf1d74e716b105598fa",
"shasum": ""
},
"require": {
@@ -3181,9 +3180,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
"source": "https://github.com/utopia-php/abuse/tree/0.43.0"
"source": "https://github.com/utopia-php/abuse/tree/0.43.1"
},
"time": "2024-08-30T05:17:23+00:00"
"time": "2024-10-23T04:29:12+00:00"
},
{
"name": "utopia-php/analytics",
@@ -3233,16 +3232,16 @@
},
{
"name": "utopia-php/audit",
"version": "0.43.0",
"version": "0.43.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e"
"reference": "04a47dd1f5f92e2d50e971a06bcc9e759325d277"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e",
"reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/04a47dd1f5f92e2d50e971a06bcc9e759325d277",
"reference": "04a47dd1f5f92e2d50e971a06bcc9e759325d277",
"shasum": ""
},
"require": {
@@ -3274,9 +3273,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/0.43.0"
"source": "https://github.com/utopia-php/audit/tree/0.43.1"
},
"time": "2024-08-30T05:17:36+00:00"
"time": "2024-10-23T04:27:59+00:00"
},
{
"name": "utopia-php/cache",
@@ -3476,16 +3475,16 @@
},
{
"name": "utopia-php/database",
"version": "0.53.16",
"version": "0.53.20",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "6661edffeef05b59e16d102b989a72f7f78cf7de"
"reference": "e43f8ee26e06ee8812737e63642dbd7ee7c9dc04"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/6661edffeef05b59e16d102b989a72f7f78cf7de",
"reference": "6661edffeef05b59e16d102b989a72f7f78cf7de",
"url": "https://api.github.com/repos/utopia-php/database/zipball/e43f8ee26e06ee8812737e63642dbd7ee7c9dc04",
"reference": "e43f8ee26e06ee8812737e63642dbd7ee7c9dc04",
"shasum": ""
},
"require": {
@@ -3526,9 +3525,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.53.16"
"source": "https://github.com/utopia-php/database/tree/0.53.20"
},
"time": "2024-11-06T03:07:16+00:00"
"time": "2024-11-12T00:23:36+00:00"
},
{
"name": "utopia-php/domains",
@@ -3678,16 +3677,16 @@
},
{
"name": "utopia-php/framework",
"version": "dev-fix-prevent-duplicate-compression",
"version": "0.33.14",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "0d535f3820a0a73b5ba03c5af27b83c0694d8368"
"reference": "45a5a2db3602fa054096f378482c7da9936f5850"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/0d535f3820a0a73b5ba03c5af27b83c0694d8368",
"reference": "0d535f3820a0a73b5ba03c5af27b83c0694d8368",
"url": "https://api.github.com/repos/utopia-php/http/zipball/45a5a2db3602fa054096f378482c7da9936f5850",
"reference": "45a5a2db3602fa054096f378482c7da9936f5850",
"shasum": ""
},
"require": {
@@ -3719,9 +3718,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/fix-prevent-duplicate-compression"
"source": "https://github.com/utopia-php/http/tree/0.33.14"
},
"time": "2024-12-02T16:47:45+00:00"
"time": "2024-11-20T12:39:10+00:00"
},
{
"name": "utopia-php/image",
@@ -3929,16 +3928,16 @@
},
{
"name": "utopia-php/migration",
"version": "0.6.13",
"version": "0.6.12",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "68d9b0a9477755afcda607e7e8109785cae17a13"
"reference": "9a8c905af4cece5c5ec9542a5b534befce067260"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/68d9b0a9477755afcda607e7e8109785cae17a13",
"reference": "68d9b0a9477755afcda607e7e8109785cae17a13",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/9a8c905af4cece5c5ec9542a5b534befce067260",
"reference": "9a8c905af4cece5c5ec9542a5b534befce067260",
"shasum": ""
},
"require": {
@@ -3979,9 +3978,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.6.13"
"source": "https://github.com/utopia-php/migration/tree/0.6.12"
},
"time": "2024-11-26T13:57:53+00:00"
"time": "2024-11-12T00:31:53+00:00"
},
{
"name": "utopia-php/mongo",
@@ -4363,16 +4362,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.18.7",
"version": "0.18.6",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "0d9228faa1c202f9e01483e45a8950485f01a288"
"reference": "893ccf06e183f8ece2aed8dbf14d64d6ba036071"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/0d9228faa1c202f9e01483e45a8950485f01a288",
"reference": "0d9228faa1c202f9e01483e45a8950485f01a288",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/893ccf06e183f8ece2aed8dbf14d64d6ba036071",
"reference": "893ccf06e183f8ece2aed8dbf14d64d6ba036071",
"shasum": ""
},
"require": {
@@ -4412,9 +4411,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.18.7"
"source": "https://github.com/utopia-php/storage/tree/0.18.6"
},
"time": "2024-11-28T11:10:53+00:00"
"time": "2024-11-06T09:58:50+00:00"
},
{
"name": "utopia-php/swoole",
@@ -5128,16 +5127,16 @@
},
{
"name": "laravel/pint",
"version": "v1.18.3",
"version": "v1.18.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "cef51821608239040ab841ad6e1c6ae502ae3026"
"reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/cef51821608239040ab841ad6e1c6ae502ae3026",
"reference": "cef51821608239040ab841ad6e1c6ae502ae3026",
"url": "https://api.github.com/repos/laravel/pint/zipball/f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64",
"reference": "f55daaf7eb6c2f49ddf6702fb42e3091c64d8a64",
"shasum": ""
},
"require": {
@@ -5148,13 +5147,13 @@
"php": "^8.1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.65.0",
"illuminate/view": "^10.48.24",
"larastan/larastan": "^2.9.11",
"friendsofphp/php-cs-fixer": "^3.64.0",
"illuminate/view": "^10.48.20",
"larastan/larastan": "^2.9.8",
"laravel-zero/framework": "^10.4.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^1.17.0",
"pestphp/pest": "^2.36.0"
"nunomaduro/termwind": "^1.15.1",
"pestphp/pest": "^2.35.1"
},
"bin": [
"builds/pint"
@@ -5190,7 +5189,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2024-11-26T15:34:00+00:00"
"time": "2024-11-20T09:33:46+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -7578,16 +7577,16 @@
},
{
"name": "symfony/console",
"version": "v7.2.0",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf"
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf",
"reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf",
"url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"shasum": ""
},
"require": {
@@ -7651,7 +7650,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.2.0"
"source": "https://github.com/symfony/console/tree/v7.1.8"
},
"funding": [
{
@@ -7667,20 +7666,20 @@
"type": "tidelift"
}
],
"time": "2024-11-06T14:24:19+00:00"
"time": "2024-11-06T14:23:19+00:00"
},
{
"name": "symfony/filesystem",
"version": "v7.2.0",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
"reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/c835867b3c62bb05c7fe3d637c871c7ae52024d4",
"reference": "c835867b3c62bb05c7fe3d637c871c7ae52024d4",
"shasum": ""
},
"require": {
@@ -7717,7 +7716,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.2.0"
"source": "https://github.com/symfony/filesystem/tree/v7.1.6"
},
"funding": [
{
@@ -7733,20 +7732,20 @@
"type": "tidelift"
}
],
"time": "2024-10-25T15:15:23+00:00"
"time": "2024-10-25T15:11:02+00:00"
},
{
"name": "symfony/finder",
"version": "v7.2.0",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49"
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49",
"reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49",
"url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8",
"reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8",
"shasum": ""
},
"require": {
@@ -7781,7 +7780,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v7.2.0"
"source": "https://github.com/symfony/finder/tree/v7.1.6"
},
"funding": [
{
@@ -7797,20 +7796,20 @@
"type": "tidelift"
}
],
"time": "2024-10-23T06:56:12+00:00"
"time": "2024-10-01T08:31:23+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v7.2.0",
"version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50"
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
"reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85",
"reference": "85e95eeede2d41cd146146e98c9c81d9214cae85",
"shasum": ""
},
"require": {
@@ -7848,7 +7847,7 @@
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v7.2.0"
"source": "https://github.com/symfony/options-resolver/tree/v7.1.6"
},
"funding": [
{
@@ -7864,7 +7863,7 @@
"type": "tidelift"
}
],
"time": "2024-11-20T11:17:29+00:00"
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -8182,16 +8181,16 @@
},
{
"name": "symfony/process",
"version": "v7.2.0",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
"reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
"url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892",
"reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892",
"shasum": ""
},
"require": {
@@ -8223,7 +8222,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.2.0"
"source": "https://github.com/symfony/process/tree/v7.1.8"
},
"funding": [
{
@@ -8239,20 +8238,20 @@
"type": "tidelift"
}
],
"time": "2024-11-06T14:24:19+00:00"
"time": "2024-11-06T14:23:19+00:00"
},
{
"name": "symfony/string",
"version": "v7.2.0",
"version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
"reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
"url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281",
"reference": "591ebd41565f356fcd8b090fe64dbb5878f50281",
"shasum": ""
},
"require": {
@@ -8310,7 +8309,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.2.0"
"source": "https://github.com/symfony/string/tree/v7.1.8"
},
"funding": [
{
@@ -8326,7 +8325,7 @@
"type": "tidelift"
}
],
"time": "2024-11-13T13:31:26+00:00"
"time": "2024-11-13T13:31:21+00:00"
},
{
"name": "textalk/websocket",
@@ -8556,18 +8555,9 @@
"time": "2024-03-07T20:33:40+00:00"
}
],
"aliases": [
{
"package": "utopia-php/framework",
"version": "dev-fix-prevent-duplicate-compression",
"alias": "0.33.99",
"alias_normalized": "0.33.99.0"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"utopia-php/framework": 20
},
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
+24 -27
View File
@@ -76,7 +76,6 @@ services:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- ./phpunit.xml:/usr/src/code/phpunit.xml
- ./tests:/usr/src/code/tests
- ./app:/usr/src/code/app
@@ -111,7 +110,7 @@ services:
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_COMPUTE_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
@@ -157,15 +156,12 @@ services:
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_COMPUTE_SIZE_LIMIT
- _APP_COMPUTE_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
- _APP_COMPUTE_CPUS
- _APP_COMPUTE_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_RUNTIMES
- _APP_SITES_RUNTIMES
- _APP_SITES_FRAMEWORKS
- _APP_DOMAIN_SITES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_LOGGING_CONFIG
@@ -197,6 +193,8 @@ services:
- _APP_EXPERIMENT_LOGGING_PROVIDER
- _APP_EXPERIMENT_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_SHARED_TABLES_V1
- _APP_DATABASE_SHARED_NAMESPACE
appwrite-console:
<<: *x-logging
@@ -386,6 +384,7 @@ services:
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_SHARED_TABLES_V1
appwrite-worker-databases:
entrypoint: worker-databases
@@ -452,13 +451,13 @@ services:
- _APP_VCS_GITHUB_APP_NAME
- _APP_VCS_GITHUB_PRIVATE_KEY
- _APP_VCS_GITHUB_APP_ID
- _APP_COMPUTE_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
- _APP_COMPUTE_CPUS
- _APP_COMPUTE_MEMORY
- _APP_COMPUTE_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_COMPUTE_FORCE_HTTPS
- _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
@@ -482,7 +481,6 @@ services:
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_DATABASE_SHARED_TABLES
- _APP_DOMAIN_SITES
appwrite-worker-certificates:
entrypoint: worker-certificates
@@ -548,10 +546,10 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_COMPUTE_TIMEOUT
- _APP_COMPUTE_BUILD_TIMEOUT
- _APP_COMPUTE_CPUS
- _APP_COMPUTE_MEMORY
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
@@ -880,7 +878,7 @@ services:
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.7.2
image: openruntimes/executor:0.6.11
restart: unless-stopped
networks:
- appwrite
@@ -893,14 +891,13 @@ services:
# It's not possible to share mount file between 2 containers without host mount (copying is too slow)
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_IMAGE_PULL=disabled
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_COMPUTE_INACTIVE_THRESHOLD
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_COMPUTE_MAINTENANCE_INTERVAL
- OPR_EXECUTOR_NETWORK=$_APP_COMPUTE_RUNTIMES_NETWORK
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
- OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES,$_APP_SITES_RUNTIMES
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v4
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
-3
View File
@@ -24,9 +24,6 @@ class Event
public const FUNCTIONS_QUEUE_NAME = 'v1-functions';
public const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
public const SITES_QUEUE_NAME = 'v1-sites';
public const SITES_CLASS_NAME = 'SitesV1';
public const USAGE_QUEUE_NAME = 'v1-usage';
public const USAGE_CLASS_NAME = 'UsageV1';
-8
View File
@@ -152,11 +152,6 @@ class Exception extends \Exception
public const PROVIDER_CONTRIBUTION_CONFLICT = 'provider_contribution_conflict';
public const GENERAL_PROVIDER_FAILURE = 'general_provider_failure';
/** Sites */
public const SITE_NOT_FOUND = 'site_not_found';
public const SITE_FRAMEWORK_UNSUPPORTED = 'site_framework_unsupported';
public const SITE_TEMPLATE_NOT_FOUND = 'site_template_not_found';
/** Functions */
public const FUNCTION_NOT_FOUND = 'function_not_found';
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
@@ -177,9 +172,6 @@ class Exception extends \Exception
public const EXECUTION_NOT_FOUND = 'execution_not_found';
public const EXECUTION_IN_PROGRESS = 'execution_in_progress';
/** Log */
public const LOG_NOT_FOUND = 'log_not_found';
/** Databases */
public const DATABASE_NOT_FOUND = 'database_not_found';
public const DATABASE_ALREADY_EXISTS = 'database_already_exists';
@@ -349,16 +349,6 @@ class Realtime extends Adapter
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
}
break;
case 'sites':
if ($parts[2] === 'deployments') {
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
}
break;
case 'migrations':
$channels[] = 'console';
+1 -1
View File
@@ -186,7 +186,7 @@ class V21 extends Migration
$document->setAttribute('scopes', []);
// Add size attribute
$document->setAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT);
$document->setAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT);
}
return $document;
-4
View File
@@ -3,8 +3,6 @@
namespace Appwrite\Platform;
use Appwrite\Platform\Modules\Core;
use Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Sites;
use Utopia\Platform\Platform;
class Appwrite extends Platform
@@ -12,7 +10,5 @@ class Appwrite extends Platform
public function __construct()
{
parent::__construct(new Core());
$this->addModule(new Functions\Module());
$this->addModule(new Sites\Module());
}
}
@@ -1,191 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Compute;
use Appwrite\Event\Build;
use Appwrite\Extend\Exception;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
class Base extends Action
{
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github)
{
$deploymentId = ID::unique();
$entrypoint = $function->getAttribute('entrypoint', '');
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$providerBranch = $function->getAttribute('providerBranch', 'main');
$authorUrl = "https://github.com/$owner";
$repositoryUrl = "https://github.com/$owner/$repositoryName";
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
$commitDetails = [];
if ($template->isEmpty()) {
try {
$commitDetails = $github->getLatestCommit($owner, $repositoryName, $providerBranch);
} catch (\Throwable $error) {
Console::warning('Failed to get latest commit details');
Console::warning($error->getMessage());
Console::warning($error->getTraceAsString());
}
}
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'functions',
'entrypoint' => $entrypoint,
'commands' => $function->getAttribute('commands', ''),
'type' => 'vcs',
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => $function->getAttribute('repositoryId', ''),
'repositoryInternalId' => $function->getAttribute('repositoryInternalId', ''),
'providerBranchUrl' => $branchUrl,
'providerRepositoryName' => $repositoryName,
'providerRepositoryOwner' => $owner,
'providerRepositoryUrl' => $repositoryUrl,
'providerCommitHash' => $commitDetails['commitHash'] ?? '',
'providerCommitAuthorUrl' => $authorUrl,
'providerCommitAuthor' => $commitDetails['commitAuthor'] ?? '',
'providerCommitMessage' => $commitDetails['commitMessage'] ?? '',
'providerCommitUrl' => $commitDetails['commitUrl'] ?? '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $function->getAttribute('providerRootDirectory', ''),
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => true,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
}
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForConsole, Build $queueForBuilds, Document $template, GitHub $github)
{
$deploymentId = ID::unique();
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $site->getAttribute('providerRepositoryId', '');
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$providerBranch = $site->getAttribute('providerBranch', 'main');
$authorUrl = "https://github.com/$owner";
$repositoryUrl = "https://github.com/$owner/$repositoryName";
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
$commitDetails = [];
if ($template->isEmpty()) {
try {
$commitDetails = $github->getLatestCommit($owner, $repositoryName, $providerBranch);
} catch (\Throwable $error) {
Console::warning('Failed to get latest commit details');
Console::warning($error->getMessage());
Console::warning($error->getTraceAsString());
}
}
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'sites',
'buildCommand' => $site->getAttribute('buildCommand', ''),
'installCommand' => $site->getAttribute('installCommand', ''),
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
'type' => 'vcs',
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => $site->getAttribute('repositoryId', ''),
'repositoryInternalId' => $site->getAttribute('repositoryInternalId', ''),
'providerBranchUrl' => $branchUrl,
'providerRepositoryName' => $repositoryName,
'providerRepositoryOwner' => $owner,
'providerRepositoryUrl' => $repositoryUrl,
'providerCommitHash' => $commitDetails['commitHash'] ?? '',
'providerCommitAuthorUrl' => $authorUrl,
'providerCommitAuthor' => $commitDetails['commitAuthor'] ?? '',
'providerCommitMessage' => $commitDetails['commitMessage'] ?? '',
'providerCommitUrl' => $commitDetails['commitUrl'] ?? '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $site->getAttribute('providerRootDirectory', ''),
'search' => implode(' ', [$deploymentId]),
'activate' => true,
]));
// Preview deployments for sites
$projectId = $project->getId();
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
$ruleId = md5($domain);
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'deployment',
'resourceId' => $deploymentId,
'resourceInternalId' => $deployment->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
}
}
@@ -1,263 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Storage\Validator\File;
use Utopia\Storage\Validator\FileExt;
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class CreateDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'createDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/functions/:functionId/deployments')
->desc('Create deployment')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].create')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('audits.event', 'deployment.create')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createDeployment')
->label('sdk.methodType', 'upload')
->label('sdk.description', '/docs/references/functions/create-deployment.md')
->label('sdk.packaging', true)
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('functionId', '', new UID(), 'Function ID.')
->param('entrypoint', null, new Text(1028), 'Entrypoint File.', true)
->param('commands', null, new Text(8192, 0), 'Build Commands.', true)
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', skipValidation: true)
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.')
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('project')
->inject('deviceForFunctions')
->inject('deviceForLocal')
->inject('queueForBuilds')
->callback([$this, 'action']);
}
public function action(string $functionId, ?string $entrypoint, ?string $commands, mixed $code, mixed $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds)
{
$activate = \strval($activate) === 'true' || \strval($activate) === '1';
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if ($entrypoint === null) {
$entrypoint = $function->getAttribute('entrypoint', '');
}
if ($commands === null) {
$commands = $function->getAttribute('commands', '');
}
if (empty($entrypoint)) {
throw new Exception(Exception::FUNCTION_ENTRYPOINT_MISSING);
}
$file = $request->getFiles('code');
// GraphQL multipart spec adds files with index keys
if (empty($file)) {
$file = $request->getFiles(0);
}
if (empty($file)) {
throw new Exception(Exception::STORAGE_FILE_EMPTY, 'No file sent');
}
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
$fileSizeValidator = new FileSize(System::getEnv('_APP_COMPUTE_SIZE_LIMIT', '30000000'));
$upload = new Upload();
// Make sure we handle a single file and multiple files the same way
$fileName = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
$fileTmpName = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
if (!$fileExt->isValid($file['name'])) { // Check if file type is allowed
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
}
$contentRange = $request->getHeader('content-range');
$deploymentId = ID::unique();
$chunk = 1;
$chunks = 1;
if (!empty($contentRange)) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$fileSize = $request->getContentRangeSize();
$deploymentId = $request->getHeader('x-appwrite-id', $deploymentId);
// TODO make `end >= $fileSize` in next breaking version
if (is_null($start) || is_null($end) || is_null($fileSize) || $end > $fileSize) {
throw new Exception(Exception::STORAGE_INVALID_CONTENT_RANGE);
}
// TODO remove the condition that checks `$end === $fileSize` in next breaking version
if ($end === $fileSize - 1 || $end === $fileSize) {
//if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to notify it's last chunk
$chunks = $chunk = -1;
} else {
// Calculate total number of chunks based on the chunk size i.e ($rangeEnd - $rangeStart)
$chunks = (int) ceil($fileSize / ($end + 1 - $start));
$chunk = (int) ($start / ($end + 1 - $start)) + 1;
}
}
if (!$fileSizeValidator->isValid($fileSize)) { // Check if file size is exceeding allowed limit
throw new Exception(Exception::STORAGE_INVALID_FILE_SIZE);
}
if (!$upload->isValid($fileTmpName)) {
throw new Exception(Exception::STORAGE_INVALID_FILE);
}
// Save to storage
$fileSize ??= $deviceForLocal->getFileSize($fileTmpName);
$path = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
if (!$deployment->isEmpty()) {
$chunks = $deployment->getAttribute('chunksTotal', 1);
$metadata = $deployment->getAttribute('metadata', []);
if ($chunk === -1) {
$chunk = $chunks;
}
}
$chunksUploaded = $deviceForFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
if (empty($chunksUploaded)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
}
$type = $request->getHeader('x-sdk-language') === 'cli' ? 'cli' : 'manual';
if ($chunksUploaded === $chunks) {
if ($activate) {
// Remove deploy for all other deployments.
$activeDeployments = $dbForProject->find('deployments', [
Query::equal('activate', [true]),
Query::equal('resourceId', [$functionId]),
Query::equal('resourceType', ['functions'])
]);
foreach ($activeDeployments as $activeDeployment) {
$activeDeployment->setAttribute('activate', false);
$dbForProject->updateDocument('deployments', $activeDeployment->getId(), $activeDeployment);
}
}
$fileSize = $deviceForFunctions->getFileSize($path);
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'buildInternalId' => '',
'entrypoint' => $entrypoint,
'commands' => $commands,
'path' => $path,
'size' => $fileSize,
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
'metadata' => $metadata,
'type' => $type
]));
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata));
}
// Start the build
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment);
} else {
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'buildInternalId' => '',
'entrypoint' => $entrypoint,
'commands' => $commands,
'path' => $path,
'size' => $fileSize,
'chunksTotal' => $chunks,
'chunksUploaded' => $chunksUploaded,
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
'metadata' => $metadata,
'type' => $type
]));
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('chunksUploaded', $chunksUploaded)->setAttribute('metadata', $metadata));
}
}
$metadata = null;
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
}
}
@@ -1,317 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Rule;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class CreateFunction extends Base
{
use HTTP;
public static function getName()
{
return 'createFunction';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/functions')
->desc('Create function')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].create')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('audits.event', 'function.create')
->label('audits.resource', 'function/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/functions/create-function.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new CustomId(), 'Function ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true)
->param('enabled', true, new Boolean(), 'Is function enabled? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true)
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function.', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.', true)
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github)
{
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
if (!empty($allowList) && !\in_array($runtime, $allowList)) {
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $runtime . '" is not supported');
}
// build from template
$template = new Document([]);
if (
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateVersion)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('version', $templateVersion);
}
$installation = $dbForConsole->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($providerRepositoryId) && (empty($installationId) || empty($providerBranch))) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
$function = $dbForProject->createDocument('functions', new Document([
'$id' => $functionId,
'execute' => $execute,
'enabled' => $enabled,
'live' => true,
'logging' => $logging,
'name' => $name,
'runtime' => $runtime,
'deploymentInternalId' => '',
'deployment' => '',
'events' => $events,
'schedule' => $schedule,
'scheduleInternalId' => '',
'scheduleId' => '',
'timeout' => $timeout,
'entrypoint' => $entrypoint,
'commands' => $commands,
'scopes' => $scopes,
'search' => implode(' ', [$functionId, $name, $runtime]),
'version' => 'v4',
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => '',
'repositoryInternalId' => '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'specification' => $specification
]));
$schedule = Authorization::skip(
fn () => $dbForConsole->createDocument('schedules', new Document([
'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $function->getAttribute('schedule'),
'active' => false,
]))
);
$function->setAttribute('scheduleId', $schedule->getId());
$function->setAttribute('scheduleInternalId', $schedule->getInternalId());
// Git connect logic
if (!empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'function',
'providerPullRequestIds' => []
]));
$function->setAttribute('repositoryId', $repository->getId());
$function->setAttribute('repositoryInternalId', $repository->getInternalId());
}
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
if (!empty($providerRepositoryId)) {
// Deploy VCS
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint', ''),
'commands' => $function->getAttribute('commands', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint', '')]),
'activate' => true,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
}
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (!empty($functionsDomain)) {
$routeSubdomain = ID::unique();
$domain = "{$routeSubdomain}.{$functionsDomain}";
$ruleId = md5($domain);
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
/** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
$ruleCreate
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger();
/** Trigger Functions */
$ruleCreate
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].create', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
}
$queueForEvents->setParam('functionId', $function->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($function, Response::MODEL_FUNCTION);
}
}
@@ -1,256 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class UpdateFunction extends Base
{
use HTTP;
public static function getName()
{
return 'updateFunction';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
->setHttpPath('/v1/functions/:functionId')
->desc('Update function')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].update')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('audits.event', 'function.update')
->label('audits.resource', 'function/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'update')
->label('sdk.description', '/docs/references/functions/update-function.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.', true)
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum execution time in seconds.', true)
->param('enabled', true, new Boolean(), 'Is function enabled? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true)
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true)
->param('providerRepositoryId', null, new Nullable(new Text(128, 0)), 'Repository ID of the repo linked to the function', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github)
{
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$installation = $dbForConsole->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($providerRepositoryId) && (empty($installationId) || empty($providerBranch))) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if (empty($runtime)) {
$runtime = $function->getAttribute('runtime');
}
$enabled ??= $function->getAttribute('enabled', true);
$repositoryId = $function->getAttribute('repositoryId', '');
$repositoryInternalId = $function->getAttribute('repositoryInternalId', '');
if (empty($entrypoint)) {
$entrypoint = $function->getAttribute('entrypoint', '');
}
$isConnected = !empty($function->getAttribute('providerRepositoryId', ''));
// Git disconnect logic. Disconnecting only when providerRepositoryId is empty, allowing for continue updates without disconnecting git
if ($isConnected && ($providerRepositoryId !== null && empty($providerRepositoryId))) {
$repositories = $dbForConsole->find('repositories', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('resourceInternalId', [$function->getInternalId()]),
Query::equal('resourceType', ['function']),
Query::limit(100),
]);
foreach ($repositories as $repository) {
$dbForConsole->deleteDocument('repositories', $repository->getId());
}
$providerRepositoryId = '';
$installationId = '';
$providerBranch = '';
$providerRootDirectory = '';
$providerSilentMode = true;
$repositoryId = '';
$repositoryInternalId = '';
}
// Git connect logic
if (!$isConnected && !empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'function',
'providerPullRequestIds' => []
]));
$repositoryId = $repository->getId();
$repositoryInternalId = $repository->getInternalId();
}
$live = true;
if (
$function->getAttribute('name') !== $name ||
$function->getAttribute('entrypoint') !== $entrypoint ||
$function->getAttribute('commands') !== $commands ||
$function->getAttribute('providerRootDirectory') !== $providerRootDirectory ||
$function->getAttribute('runtime') !== $runtime
) {
$live = false;
}
$spec = Config::getParam('runtime-specifications')[$specification] ?? [];
// Enforce Cold Start if spec limits change.
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$executor->deleteRuntime($project->getId(), $function->getAttribute('deployment'));
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
if ($th->getCode() !== 404) {
throw $th;
}
}
}
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'execute' => $execute,
'name' => $name,
'runtime' => $runtime,
'events' => $events,
'schedule' => $schedule,
'timeout' => $timeout,
'enabled' => $enabled,
'live' => $live,
'logging' => $logging,
'entrypoint' => $entrypoint,
'commands' => $commands,
'scopes' => $scopes,
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => $repositoryId,
'repositoryInternalId' => $repositoryInternalId,
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'specification' => $specification,
'search' => implode(' ', [$functionId, $name, $runtime]),
])));
// Redeploy logic
if (!$isConnected && !empty($providerRepositoryId)) {
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github);
}
// Inform scheduler if function is still active
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
$queueForEvents->setParam('functionId', $function->getId());
$response->dynamic($function, Response::MODEL_FUNCTION);
}
}
@@ -1,16 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Functions\Services\Http;
use Appwrite\Platform\Modules\Functions\Services\Workers;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
$this->addService('workers', new Workers());
}
}
@@ -1,19 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Functions\Services;
use Appwrite\Platform\Modules\Functions\Http\Deployments\CreateDeployment;
use Appwrite\Platform\Modules\Functions\Http\Functions\CreateFunction;
use Appwrite\Platform\Modules\Functions\Http\Functions\UpdateFunction;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
$this->addAction(CreateFunction::getName(), new CreateFunction());
$this->addAction(UpdateFunction::getName(), new UpdateFunction());
$this->addAction(CreateDeployment::getName(), new CreateDeployment());
}
}
@@ -1,15 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Functions\Services;
use Appwrite\Platform\Modules\Functions\Workers\Builds;
use Utopia\Platform\Service;
class Workers extends Service
{
public function __construct()
{
$this->type = Service::TYPE_WORKER;
$this->addAction(Builds::getName(), new Builds());
}
}
@@ -1,124 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class CancelDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'cancelDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/build')
->desc('Cancel deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'updateDeploymentBuild')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUILD)
->param('siteId', '', new UID(), 'Site ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
$buildId = ID::unique();
$build = $dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
'$permissions' => [],
'startTime' => DateTime::now(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'status' => 'canceled',
'path' => '',
'runtime' => $site->getAttribute('framework'),
'source' => $deployment->getAttribute('path', ''),
'sourceType' => '',
'logs' => '',
'duration' => 0,
'size' => 0
]));
$deployment->setAttribute('buildId', $build->getId());
$deployment->setAttribute('buildInternalId', $build->getInternalId());
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
} else {
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
}
$startTime = new \DateTime($build->getAttribute('startTime'));
$endTime = new \DateTime('now');
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
'endTime' => DateTime::now(),
'duration' => $duration,
'status' => 'canceled'
]));
}
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
try {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
if ($th->getCode() !== 404) {
throw $th;
}
}
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('deploymentId', $deployment->getId());
$response->dynamic($build, Response::MODEL_BUILD);
}
}
@@ -1,310 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Storage\Validator\File;
use Utopia\Storage\Validator\FileExt;
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class CreateDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'createDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/sites/:siteId/deployments')
->desc('Create deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].deployments.[deploymentId].create')
->label('audits.event', 'deployment.create')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'createDeployment')
->label('sdk.methodType', 'upload')
->label('sdk.description', '/docs/references/sites/create-deployment.md') //TODO: Create new docs
->label('sdk.packaging', true)
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('siteId', '', new UID(), 'Site ID.')
->param('installCommand', null, new Text(8192, 0), 'Install Commands.', true)
->param('buildCommand', null, new Text(8192, 0), 'Build Commands.', true)
->param('outputDirectory', null, new Text(8192, 0), 'Output Directory.', true)
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', skipValidation: true)
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.')
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->inject('project')
->inject('queueForEvents')
->inject('deviceForSites')
->inject('deviceForFunctions') // TODO: Remove this later once volume is added to executor
->inject('deviceForLocal')
->inject('queueForBuilds')
->callback([$this, 'action']);
}
public function action(string $siteId, ?string $installCommand, ?string $buildCommand, ?string $outputDirectory, mixed $code, mixed $activate, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, Event $queueForEvents, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds)
{
$activate = \strval($activate) === 'true' || \strval($activate) === '1';
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
if ($installCommand === null) {
$installCommand = $site->getAttribute('installCommand', '');
}
if ($buildCommand === null) {
$buildCommand = $site->getAttribute('buildCommand', '');
}
if ($outputDirectory === null) {
$outputDirectory = $site->getAttribute('outputDirectory', '');
}
$file = $request->getFiles('code');
// GraphQL multipart spec adds files with index keys
if (empty($file)) {
$file = $request->getFiles(0);
}
if (empty($file)) {
throw new Exception(Exception::STORAGE_FILE_EMPTY, 'No file sent');
}
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
$fileSizeValidator = new FileSize(System::getEnv('_APP_COMPUTE_SIZE_LIMIT', '30000000'));
$upload = new Upload();
// Make sure we handle a single file and multiple files the same way
$fileName = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
$fileTmpName = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
if (!$fileExt->isValid($file['name'])) { // Check if file type is allowed
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
}
$contentRange = $request->getHeader('content-range');
$deploymentId = ID::unique();
$chunk = 1;
$chunks = 1;
if (!empty($contentRange)) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$fileSize = $request->getContentRangeSize();
$deploymentId = $request->getHeader('x-appwrite-id', $deploymentId);
// TODO make `end >= $fileSize` in next breaking version
if (is_null($start) || is_null($end) || is_null($fileSize) || $end > $fileSize) {
throw new Exception(Exception::STORAGE_INVALID_CONTENT_RANGE);
}
// TODO remove the condition that checks `$end === $fileSize` in next breaking version
if ($end === $fileSize - 1 || $end === $fileSize) {
//if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to notify it's last chunk
$chunks = $chunk = -1;
} else {
// Calculate total number of chunks based on the chunk size i.e ($rangeEnd - $rangeStart)
$chunks = (int) ceil($fileSize / ($end + 1 - $start));
$chunk = (int) ($start / ($end + 1 - $start)) + 1;
}
}
if (!$fileSizeValidator->isValid($fileSize)) { // Check if file size is exceeding allowed limit
throw new Exception(Exception::STORAGE_INVALID_FILE_SIZE);
}
if (!$upload->isValid($fileTmpName)) {
throw new Exception(Exception::STORAGE_INVALID_FILE);
}
// Save to storage
$fileSize ??= $deviceForLocal->getFileSize($fileTmpName);
$path = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
if (!$deployment->isEmpty()) {
$chunks = $deployment->getAttribute('chunksTotal', 1);
$metadata = $deployment->getAttribute('metadata', []);
if ($chunk === -1) {
$chunk = $chunks;
}
}
$chunksUploaded = $deviceForFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
if (empty($chunksUploaded)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
}
$type = $request->getHeader('x-sdk-language') === 'cli' ? 'cli' : 'manual';
if ($chunksUploaded === $chunks) {
if ($activate) {
// Remove deploy for all other deployments.
$activeDeployments = $dbForProject->find('deployments', [
Query::equal('activate', [true]),
Query::equal('resourceId', [$siteId]),
Query::equal('resourceType', ['sites'])
]);
foreach ($activeDeployments as $activeDeployment) {
$activeDeployment->setAttribute('activate', false);
$dbForProject->updateDocument('deployments', $activeDeployment->getId(), $activeDeployment);
}
}
$fileSize = $deviceForFunctions->getFileSize($path);
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceInternalId' => $site->getInternalId(),
'resourceId' => $site->getId(),
'resourceType' => 'sites',
'buildInternalId' => '',
'installCommand' => $installCommand,
'buildCommand' => $buildCommand,
'outputDirectory' => $outputDirectory,
'path' => $path,
'size' => $fileSize,
'search' => implode(' ', [$deploymentId]),
'activate' => $activate,
'metadata' => $metadata,
'type' => $type
]));
// Preview deployments for sites
$projectId = $project->getId();
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
$ruleId = md5($domain);
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'deployment',
'resourceId' => $deploymentId,
'resourceInternalId' => $deployment->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata));
}
// Start the build
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment);
} else {
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceInternalId' => $site->getInternalId(),
'resourceId' => $site->getId(),
'resourceType' => 'sites',
'buildInternalId' => '',
'installCommand' => $installCommand,
'buildCommand' => $buildCommand,
'outputDirectory' => $outputDirectory,
'path' => $path,
'size' => $fileSize,
'chunksTotal' => $chunks,
'chunksUploaded' => $chunksUploaded,
'search' => implode(' ', [$deploymentId]),
'activate' => $activate,
'metadata' => $metadata,
'type' => $type
]));
// Preview deployments for sites
$projectId = $project->getId();
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
$ruleId = md5($domain);
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'deployment',
'resourceId' => $deploymentId,
'resourceInternalId' => $deployment->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('chunksUploaded', $chunksUploaded)->setAttribute('metadata', $metadata));
}
}
$metadata = null;
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('deploymentId', $deployment->getId());
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
}
}
@@ -1,96 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
class DeleteDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'deleteDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId')
->desc('Delete deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].deployments.[deploymentId].delete')
->label('audits.event', 'deployment.delete')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'deleteDeployment')
->label('sdk.description', '/docs/references/sites/delete-deployment.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('siteId', '', new UID(), 'Site ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('deviceForSites')
->inject('deviceForFunctions') //TODO: remove it later
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceForSites, Device $deviceForFunctions)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deployment->getAttribute('resourceId') !== $site->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('deployments', $deployment->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from DB');
}
if (!empty($deployment->getAttribute('path', ''))) {
if (!($deviceForFunctions->delete($deployment->getAttribute('path', '')))) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
}
}
if ($site->getAttribute('deployment') === $deployment->getId()) { // Reset site deployment
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document(array_merge($site->getArrayCopy(), [
'deployment' => '',
'deploymentInternalId' => '',
])));
}
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('deploymentId', $deployment->getId());
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($deployment);
$response->noContent();
}
}
@@ -1,119 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Swoole\Request;
class DownloadBuild extends Action
{
use HTTP;
public static function getName()
{
return 'downloadBuild';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/build/download')
->desc('Download build')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getBuildDownload')
->label('sdk.description', '/docs/references/sites/get-build-download.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('siteId', '', new UID(), 'Site ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('request')
->inject('dbForProject')
->inject('deviceForBuilds')
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForBuilds)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deployment->getAttribute('resourceId') !== $site->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId'));
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
$path = $build->getAttribute('path', '');
if (!$deviceForBuilds->exists($path)) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
$response
->setContentType('application/gzip')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
$size = $deviceForBuilds->getFileSize($path);
$rangeHeader = $request->getHeader('range');
if (!empty($rangeHeader)) {
$start = $request->getRangeStart();
$end = $request->getRangeEnd();
$unit = $request->getRangeUnit();
if ($end === null) {
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
}
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
throw new Exception(Exception::STORAGE_INVALID_RANGE);
}
$response
->addHeader('Accept-Ranges', 'bytes')
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
->addHeader('Content-Length', $end - $start + 1)
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
$response->send($deviceForBuilds->read($path, $start, ($end - $start + 1)));
}
if ($size > APP_STORAGE_READ_BUFFER) {
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceForBuilds->read(
$path,
($i * MAX_OUTPUT_CHUNK_SIZE),
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
),
(($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size
);
}
} else {
$response->send($deviceForBuilds->read($path));
}
}
}
@@ -1,115 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Swoole\Request;
class DownloadDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'downloadDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/download')
->desc('Download deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getDeploymentDownload')
->label('sdk.description', '/docs/references/sites/get-deployment-download.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('siteId', '', new UID(), 'Site ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('request')
->inject('dbForProject')
->inject('deviceForSites')
->inject('deviceForFunctions') //TODO: Remove this later
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForSites, Device $deviceForFunctions)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deployment->getAttribute('resourceId') !== $site->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$path = $deployment->getAttribute('path', '');
if (!$deviceForFunctions->exists($path)) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$response
->setContentType('application/gzip')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
$size = $deviceForFunctions->getFileSize($path);
$rangeHeader = $request->getHeader('range');
if (!empty($rangeHeader)) {
$start = $request->getRangeStart();
$end = $request->getRangeEnd();
$unit = $request->getRangeUnit();
if ($end === null) {
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
}
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
throw new Exception(Exception::STORAGE_INVALID_RANGE);
}
$response
->addHeader('Accept-Ranges', 'bytes')
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
->addHeader('Content-Length', $end - $start + 1)
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
}
if ($size > APP_STORAGE_READ_BUFFER) {
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceForFunctions->read(
$path,
($i * MAX_OUTPUT_CHUNK_SIZE),
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
),
(($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size
);
}
} else {
$response->send($deviceForFunctions->read($path));
}
}
}
@@ -1,85 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class GetDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'getDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId')
->desc('Get deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getDeployment')
->label('sdk.description', '/docs/references/sites/get-deployment.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('siteId', '', new UID(), 'Site ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Document $project, Database $dbForProject, Database $dbForConsole)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->getAttribute('resourceId') !== $site->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
$rule = Authorization::skip(fn () => $dbForConsole->findOne('rules', [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("resourceType", ["deployment"]),
Query::equal("resourceInternalId", [$deployment->getInternalId()])
]));
if (!empty($rule)) {
$deployment->setAttribute('domain', $rule->getAttribute('domain', ''));
}
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
}
}
@@ -1,129 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class ListDeployments extends Action
{
use HTTP;
public static function getName()
{
return 'listDeployments';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/deployments')
->desc('List deployments')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'listDeployments')
->label('sdk.description', '/docs/references/sites/list-deployments.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT_LIST)
->param('siteId', '', new UID(), 'Site ID.')
->param('queries', [], new Deployments(), '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(', ', Deployments::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->callback([$this, 'action']);
}
public function action(string $siteId, array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForConsole)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
// Set resource queries
$queries[] = Query::equal('resourceInternalId', [$site->getInternalId()]);
$queries[] = Query::equal('resourceType', ['sites']);
/**
* 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());
}
$deploymentId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForProject->find('deployments', $queries);
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
foreach ($results as $result) {
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
$result->setAttribute('size', $result->getAttribute('size', 0));
$rule = Authorization::skip(fn () => $dbForConsole->findOne('rules', [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("resourceType", ["deployment"]),
Query::equal("resourceInternalId", [$result->getInternalId()])
]));
if (!empty($rule)) {
$result->setAttribute('domain', $rule->getAttribute('domain', ''));
}
}
$response->dynamic(new Document([
'deployments' => $results,
'total' => $total,
]), Response::MODEL_DEPLOYMENT_LIST);
}
}
@@ -1,99 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
class RebuildDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'rebuildDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/build')
->desc('Rebuild deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'createBuild')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('siteId', '', new UID(), 'Site ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('deviceForSites')
->inject('deviceForFunctions') //TODO: remove it later
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Build $queueForBuilds, Device $deviceForSites, Device $deviceForFunctions)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$path = $deployment->getAttribute('path');
if (empty($path) || !$deviceForFunctions->exists($path)) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$deploymentId = ID::unique();
$destination = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
$deployment->removeAttribute('$internalId');
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
'$internalId' => '',
'$id' => $deploymentId,
'buildId' => '',
'buildInternalId' => '',
'path' => $destination,
'buildCommand' => $site->getAttribute('buildCommand', ''),
'installCommand' => $site->getAttribute('installCommand', ''),
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
'search' => implode(' ', [$deploymentId]),
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment);
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('deploymentId', $deployment->getId());
$response->noContent();
}
}
@@ -1,83 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class UpdateDeployment extends Action
{
use HTTP;
public static function getName()
{
return 'updateDeployment';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId')
->desc('Update deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'updateDeployment')
->label('sdk.description', '/docs/references/sites/update-site-deployment.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SITE)
->param('siteId', '', new UID(), 'Site ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('dbForConsole')
->callback([$this, 'action']);
}
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole)
{
$site = $dbForProject->getDocument('sites', $siteId);
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'ready') {
throw new Exception(Exception::BUILD_NOT_READY);
}
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document(array_merge($site->getArrayCopy(), [
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
])));
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('deploymentId', $deployment->getId());
$response->dynamic($site, Response::MODEL_SITE);
}
}
@@ -1,77 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Logs;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class DeleteLog extends Base
{
use HTTP;
public static function getName()
{
return 'deleteLog';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
->setHttpPath('/v1/sites/:siteId/logs/:logId')
->desc('Delete log')
->groups(['api', 'sites'])
->label('scope', 'log.write')
->label('resourceType', RESOURCE_TYPE_SITES)
->label('event', 'sites.[siteId].logs.[logId].delete')
->label('audits.event', 'logs.delete')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'deleteLog')
->label('sdk.description', '/docs/references/sites/delete-log.md') // TODO: add this file
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('siteId', '', new UID(), 'Site ID.')
->param('logId', '', new UID(), 'Log ID.')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(string $siteId, string $logId, Response $response, Database $dbForProject, Event $queueForEvents)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$log = $dbForProject->getDocument('executions', $logId);
if ($log->isEmpty()) {
throw new Exception(Exception::LOG_NOT_FOUND);
}
if ($log->getAttribute('resourceType') !== 'sites' && $log->getAttribute('resourceInternalId') !== $site->getInternalId()) {
throw new Exception(Exception::LOG_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('executions', $log->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove log from DB');
}
$queueForEvents
->setParam('siteId', $site->getId())
->setParam('logId', $log->getId())
->setPayload($response->output($log, Response::MODEL_EXECUTION)); // TODO: Update model
$response->noContent();
}
}
@@ -1,64 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Logs;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class GetLog extends Base
{
use HTTP;
public static function getName()
{
return 'getLog';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/logs/:logId')
->desc('Get log')
->groups(['api', 'sites'])
->label('scope', 'log.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getLog')
->label('sdk.description', '/docs/references/sites/get-log.md') // TODO: add this file
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION)
->param('siteId', '', new UID(), 'Site ID.')
->param('logId', '', new UID(), 'Log ID.')
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, string $logId, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty() || !$site->getAttribute('enabled')) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$log = $dbForProject->getDocument('executions', $logId);
if ($log->getAttribute('resourceType') !== 'sites' && $log->getAttribute('resourceInternalId') !== $site->getInternalId()) {
throw new Exception(Exception::LOG_NOT_FOUND);
}
if ($log->isEmpty()) {
throw new Exception(Exception::LOG_NOT_FOUND);
}
$response->dynamic($log, Response::MODEL_EXECUTION); //TODO: Change to model log, but model log already exists - decide what to do
}
}
@@ -1,110 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Logs;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Database\Validator\Queries\Executions;
use Appwrite\Utopia\Database\Validator\Queries\Logs;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class ListLogs extends Base
{
use HTTP;
public static function getName()
{
return 'listLogs';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/logs')
->desc('List logs')
->groups(['api', 'sites'])
->label('scope', 'log.read')
->label('resourceType', RESOURCE_TYPE_SITES)
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'listLogs')
->label('sdk.description', '/docs/references/sites/list-logs.md') // TODO: add this file
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION_LIST) // TODO: Update this later
->param('siteId', '', new UID(), 'Site ID.')
->param('queries', [], new Logs(), '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(', ', Executions::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, array $queries, string $search, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty() || !$site->getAttribute('enabled')) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
// Set internal queries
$queries[] = Query::equal('resourceInternalId', [$site->getInternalId()]);
$queries[] = Query::equal('resourceType', ['sites']);
/**
* 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());
}
$logId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('executions', $logId);
if ($cursorDocument->isEmpty() || $cursorDocument->getAttribute('resourceType') !== 'sites') {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Log '{$logId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForProject->find('executions', $queries);
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
$response->dynamic(new Document([
'executions' => $results,
'total' => $total,
]), Response::MODEL_EXECUTION_LIST); // TODO: Update response model
}
}
@@ -1,331 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Sites\Validator\FrameworkSpecification;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Rule;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class CreateSite extends Base
{
use HTTP;
public static function getName()
{
return 'createSite';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/sites')
->desc('Create site')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].create')
->label('audits.event', 'site.create')
->label('audits.resource', 'site/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/sites/create-site.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SITE)
->param('siteId', '', new CustomId(), 'Site ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Site name. Max length: 128 chars.')
->param('framework', '', new WhiteList(array_keys(Config::getParam('frameworks')), true), 'Sites framework.')
->param('enabled', true, new Boolean(), 'Is site enabled? When set to \'disabled\', users cannot access the site but Server SDKs with and API key can still access the site. No data is lost when this is toggled.', true) // TODO: Add logging param later
->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true)
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
->param('subdomain', '', new CustomId(), 'Unique custom sub-domain. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true)
->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.')
->param('adapter', '', new Text(8192, 0), 'Framework adapter. Allows: static, ssr', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
->param('fallbackFile', '', new Text(255, 0), 'Fallback file for single page application sites.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the site.', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to site code in the template repo.', true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the site template.', true)
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
$plan,
Config::getParam('framework-specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Framework specification for the site and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $subdomain, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github)
{
if (!empty($adapter)) {
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
$adapters = \array_keys($configFramework['adapters'] ?? []);
$validator = new WhiteList($adapters, true);
if (!$validator->isValid($adapter)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Adapter not supported for the selected framework.');
}
}
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$routeSubdomain = '';
$domain = '';
if (!empty($sitesDomain)) {
$routeSubdomain = $subdomain ?: ID::unique();
$domain = "{$routeSubdomain}.{$sitesDomain}";
$subdomain = Authorization::skip(fn () => $dbForConsole->getDocument('rules', \md5($domain)));
if ($subdomain && !$subdomain->isEmpty()) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Subdomain already exists. Please choose a different subdomain.');
}
}
$siteId = ($siteId == 'unique()') ? ID::unique() : $siteId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_SITES_FRAMEWORKS', '')));
if (!empty($allowList) && !\in_array($framework, $allowList)) {
throw new Exception(Exception::SITE_FRAMEWORK_UNSUPPORTED, 'Framework "' . $framework . '" is not supported');
}
// build from template
$template = new Document([]);
if (
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateVersion)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('version', $templateVersion);
}
$installation = $dbForConsole->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($providerRepositoryId) && (empty($installationId) || empty($providerBranch))) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
$site = $dbForProject->createDocument('sites', new Document([
'$id' => $siteId,
'enabled' => $enabled,
'live' => true,
'name' => $name,
'framework' => $framework,
'deploymentInternalId' => '',
'deploymentId' => '',
'timeout' => $timeout,
'installCommand' => $installCommand,
'buildCommand' => $buildCommand,
'outputDirectory' => $outputDirectory,
'search' => implode(' ', [$siteId, $name, $framework]),
'fallbackFile' => $fallbackFile,
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => '',
'repositoryInternalId' => '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'specification' => $specification,
'buildRuntime' => $buildRuntime,
'adapter' => $adapter,
]));
// Git connect logic
if (!empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'site',
'providerPullRequestIds' => []
]));
$site->setAttribute('repositoryId', $repository->getId());
$site->setAttribute('repositoryInternalId', $repository->getInternalId());
}
$site = $dbForProject->updateDocument('sites', $site->getId(), $site);
if (!empty($providerRepositoryId)) {
// Deploy VCS
$this->redeployVcsSite($request, $site, $project, $installation, $dbForProject, $dbForConsole, $queueForBuilds, $template, $github);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'sites',
'installCommand' => $site->getAttribute('installCommand', ''),
'buildCommand' => $site->getAttribute('buildCommand', ''),
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId]),
'activate' => true,
]));
// Preview deployments url
$projectId = $project->getId();
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$previewDomain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => \md5($previewDomain),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $previewDomain,
'resourceType' => 'deployment',
'resourceId' => $deploymentId,
'resourceInternalId' => $deployment->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
}
if (!empty($sitesDomain)) {
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => \md5($domain),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'site',
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
/** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
$ruleCreate
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger();
/** Trigger Sites */
$ruleCreate
->setClass(Event::SITES_CLASS_NAME)
->setQueue(Event::SITES_QUEUE_NAME)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].create', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
}
$queueForEvents->setParam('siteId', $site->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($site, Response::MODEL_SITE);
}
}
@@ -1,69 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class DeleteSite extends Base
{
use HTTP;
public static function getName()
{
return 'deleteSite';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
->setHttpPath('/v1/sites/:siteId')
->desc('Delete site')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].delete')
->label('audits.event', 'site.delete')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/sites/delete-site.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('siteId', '', new UID(), 'Site ID.')
->inject('response')
->inject('dbForProject')
->inject('queueForDeletes')
->inject('queueForEvents')
->callback([$this, 'action']);
}
public function action(string $siteId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
if (!$dbForProject->deleteDocument('sites', $site->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove site from DB');
}
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($site);
$queueForEvents->setParam('siteId', $site->getId());
$response->noContent();
}
}
@@ -1,54 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class GetSite extends Base
{
use HTTP;
public static function getName()
{
return 'getSite';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId')
->desc('Get site')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('resourceType', RESOURCE_TYPE_SITES)
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/sites/get-site.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SITE)
->param('siteId', '', new UID(), 'Site ID.')
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$response->dynamic($site, Response::MODEL_SITE);
}
}
@@ -1,129 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class GetSiteUsage extends Base
{
use HTTP;
public static function getName()
{
return 'getSiteUsage';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/usage')
->desc('Get site usage')
->groups(['api', 'sites', 'usage'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getSiteUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_SITE)
->param('siteId', '', new UID(), 'Site ID.')
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, string $range, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
str_replace(['{resourceType}', '{resourceInternalId}'], ['sites', $site->getInternalId()], METRIC_SITE_ID_DEPLOYMENTS),
str_replace(['{resourceType}', '{resourceInternalId}'], ['sites', $site->getInternalId()], METRIC_SITE_ID_DEPLOYMENTS_STORAGE),
str_replace('{siteInternalId}', $site->getInternalId(), METRIC_SITE_ID_BUILDS),
str_replace('{siteInternalId}', $site->getInternalId(), METRIC_SITE_ID_BUILDS_STORAGE),
str_replace('{siteInternalId}', $site->getInternalId(), METRIC_SITE_ID_BUILDS_COMPUTE),
str_replace('{siteInternalId}', $site->getInternalId(), METRIC_SITE_ID_BUILDS_MB_SECONDS)
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric]['data'] = [];
foreach ($results as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
}
});
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
}
$response->dynamic(new Document([
'range' => $range,
'deploymentsTotal' => $usage[$metrics[0]]['total'],
'deploymentsStorageTotal' => $usage[$metrics[1]]['total'],
'buildsTotal' => $usage[$metrics[2]]['total'],
'buildsStorageTotal' => $usage[$metrics[3]]['total'],
'buildsTimeTotal' => $usage[$metrics[4]]['total'],
'deployments' => $usage[$metrics[0]]['data'],
'deploymentsStorage' => $usage[$metrics[1]]['data'],
'builds' => $usage[$metrics[2]]['data'],
'buildsStorage' => $usage[$metrics[3]]['data'],
'buildsTime' => $usage[$metrics[4]]['data'],
'buildsMbSecondsTotal' => $usage[$metrics[7]]['total'],
'buildsMbSeconds' => $usage[$metrics[7]]['data']
// TODO: Add more metrics for requests, bandwidth, etc.
]), Response::MODEL_USAGE_SITE);
}
}
@@ -1,121 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class GetSitesUsage extends Base
{
use HTTP;
public static function getName()
{
return 'getSitesUsage';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/usage')
->desc('Get sites usage')
->groups(['api', 'sites', 'usage'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_SITES)
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $range, Response $response, Database $dbForProject)
{
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
METRIC_SITES,
METRIC_DEPLOYMENTS,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS,
METRIC_BUILDS_STORAGE,
METRIC_BUILDS_COMPUTE,
METRIC_BUILDS_MB_SECONDS,
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric]['data'] = [];
foreach ($results as $result) {
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
}
});
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
}
}
$response->dynamic(new Document([
'range' => $range,
'sitesTotal' => $usage[$metrics[0]]['total'],
'deploymentsTotal' => $usage[$metrics[1]]['total'],
'deploymentsStorageTotal' => $usage[$metrics[2]]['total'],
'buildsTotal' => $usage[$metrics[3]]['total'],
'buildsStorageTotal' => $usage[$metrics[4]]['total'],
'buildsTimeTotal' => $usage[$metrics[5]]['total'],
'sites' => $usage[$metrics[0]]['data'],
'deployments' => $usage[$metrics[1]]['data'],
'deploymentsStorage' => $usage[$metrics[2]]['data'],
'builds' => $usage[$metrics[3]]['data'],
'buildsStorage' => $usage[$metrics[4]]['data'],
'buildsTime' => $usage[$metrics[5]]['data'],
'buildsMbSecondsTotal' => $usage[$metrics[8]]['total'],
'buildsMbSeconds' => $usage[$metrics[8]]['data']
]), Response::MODEL_USAGE_SITES);
}
}
@@ -1,58 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class GetTemplate extends Base
{
use HTTP;
public static function getName()
{
return 'getTemplate';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/templates/:templateId')
->desc('Get site template')
->groups(['api'])
->label('scope', 'public')
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getTemplate')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.description', '/docs/references/sites/get-template.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEMPLATE_SITE)
->param('templateId', '', new Text(128), 'Template ID.')
->inject('response')
->callback([$this, 'action']);
}
public function action(string $templateId, Response $response)
{
$templates = Config::getParam('site-templates', []);
$allowedTemplates = \array_filter($templates, function ($item) use ($templateId) {
return $item['key'] === $templateId;
});
$template = array_shift($allowedTemplates);
if (empty($template)) {
throw new Exception(Exception::SITE_TEMPLATE_NOT_FOUND);
}
$response->dynamic(new Document($template), Response::MODEL_TEMPLATE_SITE);
}
}
@@ -1,61 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
class ListFrameworks extends Base
{
use HTTP;
public static function getName()
{
return 'listFrameworks';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/frameworks')
->desc('List frameworks')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'listFrameworks')
->label('sdk.description', '/docs/references/sites/list-frameworks.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FRAMEWORK_LIST)
->inject('response')
->callback([$this, 'action']);
}
public function action(Response $response)
{
$frameworks = Config::getParam('frameworks');
$allowList = \array_filter(\explode(',', System::getEnv('_APP_SITES_FRAMEWORKS', '')));
$allowed = [];
foreach ($frameworks as $id => $framework) {
if (!empty($allowList) && !\in_array($id, $allowList)) {
continue;
}
$allowed[] = $framework;
}
$response->dynamic(new Document([
'total' => count($allowed),
'frameworks' => $allowed
]), Response::MODEL_FRAMEWORK_LIST);
}
}
@@ -1,93 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Database\Validator\Queries\Sites;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class ListSites extends Base
{
use HTTP;
public static function getName()
{
return 'listSites';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites')
->desc('List sites')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/sites/list-sites.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SITE_LIST)
->param('queries', [], new Sites(), '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(', ', Sites::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(array $queries, string $search, Response $response, Database $dbForProject)
{
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$siteId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('sites', $siteId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Site '{$siteId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'sites' => $dbForProject->find('sites', $queries),
'total' => $dbForProject->count('sites', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_SITE_LIST);
}
}
@@ -1,69 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
class ListTemplates extends Base
{
use HTTP;
public static function getName()
{
return 'listTemplates';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/templates')
->desc('List templates')
->groups(['api'])
->label('scope', 'public')
->label('sdk.namespace', 'sites')
->label('sdk.method', 'listTemplates')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.description', '/docs/references/sites/list-templates.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEMPLATE_SITE_LIST)
->param('frameworks', [], new ArrayList(new WhiteList(array_keys(Config::getParam('frameworks')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of frameworks allowed for filtering site templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' frameworks are allowed.', true)
->param('useCases', [], new ArrayList(new WhiteList(['dev-tools', 'starter', 'databases', 'ai', 'messaging', 'utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering site templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true)
->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true)
->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true)
->inject('response')
->callback([$this, 'action']);
}
public function action(array $frameworks, array $usecases, int $limit, int $offset, Response $response)
{
$templates = Config::getParam('site-templates', []);
if (!empty($frameworks)) {
$templates = \array_filter($templates, function ($template) use ($frameworks) {
return \count(\array_intersect($frameworks, \array_column($template['frameworks'], 'name'))) > 0;
});
}
if (!empty($usecases)) {
$templates = \array_filter($templates, function ($template) use ($usecases) {
return \count(\array_intersect($usecases, $template['useCases'])) > 0;
});
}
$responseTemplates = \array_slice($templates, $offset, $limit);
$response->dynamic(new Document([
'templates' => $responseTemplates,
'total' => \count($responseTemplates),
]), Response::MODEL_TEMPLATE_SITE_LIST);
}
}
@@ -1,244 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Sites\Validator\FrameworkSpecification;
use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class UpdateSite extends Base
{
use HTTP;
public static function getName()
{
return 'updateSite';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
->setHttpPath('/v1/sites/:siteId')
->desc('Update site')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('event', 'sites.[siteId].update')
->label('audits.event', 'sites.update')
->label('audits.resource', 'site/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'update')
->label('sdk.description', '/docs/references/sites/update-site.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SITE)
->param('siteId', '', new UID(), 'Site ID.')
->param('name', '', new Text(128), 'Site name. Max length: 128 chars.')
->param('framework', '', new WhiteList(array_keys(Config::getParam('frameworks')), true), 'Sites framework.')
->param('enabled', true, new Boolean(), 'Is site enabled? When set to \'disabled\', users cannot access the site but Server SDKs with and API key can still access the site. No data is lost when this is toggled.', true) // TODO: Add logging param later
->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true)
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.', true)
->param('adapter', '', new Text(8192, 0), 'Framework adapter. Usuallly allows: static, ssr', true)
->param('fallbackFile', '', new Text(255, 0), 'Fallback file for single page application sites.', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the site.', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
$plan,
Config::getParam('framework-specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Framework specification for the site and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->callback([$this, 'action']);
}
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, ?string $fallbackFile, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github)
{
if (!empty($adapter)) {
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
$adapters = \array_keys($configFramework['adapters'] ?? []);
$validator = new WhiteList($adapters, true);
if (!$validator->isValid($adapter)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Adapter not supported for the selected framework.');
}
}
// TODO: If only branch changes, re-deploy
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$installation = $dbForConsole->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($providerRepositoryId) && (empty($installationId) || empty($providerBranch))) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
if (empty($framework)) {
$framework = $site->getAttribute('framework');
}
$enabled ??= $site->getAttribute('enabled', true);
$repositoryId = $site->getAttribute('repositoryId', '');
$repositoryInternalId = $site->getAttribute('repositoryInternalId', '');
$isConnected = !empty($site->getAttribute('providerRepositoryId', ''));
// Git disconnect logic. Disconnecting only when providerRepositoryId is empty, allowing for continue updates without disconnecting git
if ($isConnected && ($providerRepositoryId !== null && empty($providerRepositoryId))) {
$repositories = $dbForConsole->find('repositories', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('resourceInternalId', [$site->getInternalId()]),
Query::equal('resourceType', ['site']),
Query::limit(100),
]);
foreach ($repositories as $repository) {
$dbForConsole->deleteDocument('repositories', $repository->getId());
}
$providerRepositoryId = '';
$installationId = '';
$providerBranch = '';
$providerRootDirectory = '';
$providerSilentMode = true;
$repositoryId = '';
$repositoryInternalId = '';
}
// Git connect logic
if (!$isConnected && !empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'site',
'providerPullRequestIds' => []
]));
$repositoryId = $repository->getId();
$repositoryInternalId = $repository->getInternalId();
}
$live = true;
if (
$site->getAttribute('name') !== $name ||
$site->getAttribute('buildCommand') !== $buildCommand ||
$site->getAttribute('installCommand') !== $installCommand ||
$site->getAttribute('outputDirectory') !== $outputDirectory ||
$site->getAttribute('providerRootDirectory') !== $providerRootDirectory ||
$site->getAttribute('framework') !== $framework
) {
$live = false;
}
$spec = Config::getParam('framework-specifications')[$specification] ?? [];
// Enforce Cold Start if spec limits change.
if ($site->getAttribute('specification') !== $specification && !empty($site->getAttribute('deploymentId'))) {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$executor->deleteRuntime($project->getId(), $site->getAttribute('deploymentId'));
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
if ($th->getCode() !== 404) {
throw $th;
}
}
}
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document(array_merge($site->getArrayCopy(), [
'name' => $name,
'framework' => $framework,
'enabled' => $enabled,
'live' => $live,
'timeout' => $timeout,
'installCommand' => $installCommand,
'buildCommand' => $buildCommand,
'outputDirectory' => $outputDirectory,
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => $repositoryId,
'repositoryInternalId' => $repositoryInternalId,
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'specification' => $specification,
'search' => implode(' ', [$siteId, $name, $framework]),
'buildRuntime' => $buildRuntime,
'adapter' => $adapter,
'fallbackFile' => $fallbackFile,
])));
// Redeploy logic
if (!$isConnected && !empty($providerRepositoryId)) {
$this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github);
}
$queueForEvents->setParam('siteId', $site->getId());
$response->dynamic($site, Response::MODEL_SITE);
}
}
@@ -1,94 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class CreateVariable extends Base
{
use HTTP;
public static function getName()
{
return 'createVariable';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/sites/:siteId/variables')
->desc('Create variable')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('audits.event', 'variable.create')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'createVariable')
->label('sdk.description', '/docs/references/sites/create-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->param('siteId', '', new UID(), 'Site unique ID.', false)
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->callback([$this, 'action']);
}
public function action(string $siteId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForConsole)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$variableId = ID::unique();
$variable = new Document([
'$id' => $variableId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceInternalId' => $site->getInternalId(),
'resourceId' => $site->getId(),
'resourceType' => 'site',
'key' => $key,
'value' => $value,
'secret' => $secret,
'search' => implode(' ', [$variableId, $site->getId(), $key, 'site']),
]);
try {
$variable = $dbForProject->createDocument('variables', $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
}
$dbForProject->updateDocument('sites', $site->getId(), $site->setAttribute('live', false));
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($variable, Response::MODEL_VARIABLE);
}
}
@@ -1,68 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class DeleteVariable extends Base
{
use HTTP;
public static function getName()
{
return 'deleteVariable';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
->setHttpPath('/v1/sites/:siteId/variables/:variableId')
->desc('Delete variable')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('audits.event', 'variable.delete')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'deleteVariable')
->label('sdk.description', '/docs/references/sites/delete-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('siteId', '', new UID(), 'Site unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, string $variableId, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $site->getInternalId() || $variable->getAttribute('resourceType') !== 'site') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
$dbForProject->deleteDocument('variables', $variable->getId());
$dbForProject->updateDocument('sites', $site->getId(), $site->setAttribute('live', false));
$response->noContent();
}
}
@@ -1,68 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class GetVariable extends Base
{
use HTTP;
public static function getName()
{
return 'getVariable';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/variables/:variableId')
->desc('Get variable')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'getVariable')
->label('sdk.description', '/docs/references/sites/get-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->param('siteId', '', new UID(), 'Site unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, string $variableId, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$variable = $dbForProject->getDocument('variables', $variableId);
if (
$variable === false ||
$variable->isEmpty() ||
$variable->getAttribute('resourceInternalId') !== $site->getInternalId() ||
$variable->getAttribute('resourceType') !== 'site'
) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
$response->dynamic($variable, Response::MODEL_VARIABLE);
}
}
@@ -1,57 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class ListVariables extends Base
{
use HTTP;
public static function getName()
{
return 'listVariables';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/:siteId/variables')
->desc('List variables')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'listVariables')
->label('sdk.description', '/docs/references/sites/list-variables.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE_LIST)
->param('siteId', '', new UID(), 'Site unique ID.', false)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$response->dynamic(new Document([
'variables' => $site->getAttribute('vars', []),
'total' => \count($site->getAttribute('vars', [])),
]), Response::MODEL_VARIABLE_LIST);
}
}
@@ -1,82 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class UpdateVariable extends Base
{
use HTTP;
public static function getName()
{
return 'updateVariable';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
->setHttpPath('/v1/sites/:siteId/variables/:variableId')
->desc('Update variable')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('audits.event', 'variable.update')
->label('audits.resource', 'site/{request.siteId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'updateVariable')
->label('sdk.description', '/docs/references/sites/update-variable.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
->param('siteId', '', new UID(), 'Site unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $siteId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject)
{
$site = $dbForProject->getDocument('sites', $siteId);
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $site->getInternalId() || $variable->getAttribute('resourceType') !== 'site') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
$variable
->setAttribute('key', $key)
->setAttribute('value', $value ?? $variable->getAttribute('value'))
->setAttribute('search', implode(' ', [$variableId, $site->getId(), $key, 'site']));
try {
$dbForProject->updateDocument('variables', $variable->getId(), $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
}
$dbForProject->updateDocument('sites', $site->getId(), $site->setAttribute('live', false));
$response->dynamic($variable, Response::MODEL_VARIABLE);
}
}
@@ -1,14 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites;
use Appwrite\Platform\Modules\Sites\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -1,81 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Services;
use Appwrite\Platform\Modules\Sites\Http\Deployments\CancelDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\CreateDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\DeleteDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\DownloadBuild;
use Appwrite\Platform\Modules\Sites\Http\Deployments\DownloadDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\GetDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\ListDeployments;
use Appwrite\Platform\Modules\Sites\Http\Deployments\RebuildDeployment;
use Appwrite\Platform\Modules\Sites\Http\Deployments\UpdateDeployment;
use Appwrite\Platform\Modules\Sites\Http\Logs\DeleteLog;
use Appwrite\Platform\Modules\Sites\Http\Logs\GetLog;
use Appwrite\Platform\Modules\Sites\Http\Logs\ListLogs;
use Appwrite\Platform\Modules\Sites\Http\Sites\CreateSite;
use Appwrite\Platform\Modules\Sites\Http\Sites\DeleteSite;
use Appwrite\Platform\Modules\Sites\Http\Sites\GetSite;
use Appwrite\Platform\Modules\Sites\Http\Sites\GetSitesUsage;
use Appwrite\Platform\Modules\Sites\Http\Sites\GetSiteUsage;
use Appwrite\Platform\Modules\Sites\Http\Sites\GetTemplate;
use Appwrite\Platform\Modules\Sites\Http\Sites\ListFrameworks;
use Appwrite\Platform\Modules\Sites\Http\Sites\ListSites;
use Appwrite\Platform\Modules\Sites\Http\Sites\ListTemplates;
use Appwrite\Platform\Modules\Sites\Http\Sites\UpdateSite;
use Appwrite\Platform\Modules\Sites\Http\Variables\CreateVariable;
use Appwrite\Platform\Modules\Sites\Http\Variables\DeleteVariable;
use Appwrite\Platform\Modules\Sites\Http\Variables\GetVariable;
use Appwrite\Platform\Modules\Sites\Http\Variables\ListVariables;
use Appwrite\Platform\Modules\Sites\Http\Variables\UpdateVariable;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
// Sites
$this->addAction(CreateSite::getName(), new CreateSite());
$this->addAction(GetSite::getName(), new GetSite());
$this->addAction(ListSites::getName(), new ListSites());
$this->addAction(UpdateSite::getName(), new UpdateSite());
$this->addAction(DeleteSite::getName(), new DeleteSite());
// Frameworks
$this->addAction(ListFrameworks::getName(), new ListFrameworks());
// Deployments
$this->addAction(CreateDeployment::getName(), new CreateDeployment());
$this->addAction(GetDeployment::getName(), new GetDeployment());
$this->addAction(ListDeployments::getName(), new ListDeployments());
$this->addAction(UpdateDeployment::getName(), new UpdateDeployment());
$this->addAction(DeleteDeployment::getName(), new DeleteDeployment());
$this->addAction(DownloadDeployment::getName(), new DownloadDeployment());
$this->addAction(DownloadBuild::getName(), new DownloadBuild());
$this->addAction(RebuildDeployment::getName(), new RebuildDeployment());
$this->addAction(CancelDeployment::getName(), new CancelDeployment());
// Logs
$this->addAction(GetLog::getName(), new GetLog());
$this->addAction(ListLogs::getName(), new ListLogs());
$this->addAction(DeleteLog::getName(), new DeleteLog());
// Variables
$this->addAction(CreateVariable::getName(), new CreateVariable());
$this->addAction(GetVariable::getName(), new GetVariable());
$this->addAction(ListVariables::getName(), new ListVariables());
$this->addAction(UpdateVariable::getName(), new UpdateVariable());
$this->addAction(DeleteVariable::getName(), new DeleteVariable());
// Templates
$this->addAction(ListTemplates::getName(), new ListTemplates());
$this->addAction(GetTemplate::getName(), new GetTemplate());
// Usage
$this->addAction(GetSiteUsage::getName(), new GetSiteUsage());
$this->addAction(GetSitesUsage::getName(), new GetSitesUsage());
}
}
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Services;
use Appwrite\Platform\Workers\Audits;
use Appwrite\Platform\Workers\Builds;
use Appwrite\Platform\Workers\Certificates;
use Appwrite\Platform\Workers\Databases;
use Appwrite\Platform\Workers\Deletes;
@@ -22,6 +23,7 @@ class Workers extends Service
$this->type = Service::TYPE_WORKER;
$this
->addAction(Audits::getName(), new Audits())
->addAction(Builds::getName(), new Builds())
->addAction(Certificates::getName(), new Certificates())
->addAction(Databases::getName(), new Databases())
->addAction(Deletes::getName(), new Deletes())
+3 -3
View File
@@ -96,10 +96,10 @@ class Doctor extends Action
Console::log('🟢 HTTPS force option is enabled');
}
if ('enabled' !== System::getEnv('_APP_OPTIONS_COMPUTE_FORCE_HTTPS', 'disabled')) {
Console::log('🔴 HTTPS force option is disabled for function/site domains');
if ('enabled' !== System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled')) {
Console::log('🔴 HTTPS force option is disabled for function domains');
} else {
Console::log('🟢 HTTPS force option is enabled for function/site domains');
Console::log('🟢 HTTPS force option is enabled for function domains');
}
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
@@ -1,6 +1,6 @@
<?php
namespace Appwrite\Platform\Modules\Functions\Workers;
namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT;
use Appwrite\Event\Event;
@@ -22,7 +22,6 @@ use Utopia\Database\Exception\Conflict;
use Utopia\Database\Exception\Restricted;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
@@ -55,7 +54,7 @@ class Builds extends Action
->inject('dbForProject')
->inject('deviceForFunctions')
->inject('log')
->callback([$this, 'action']);
->callback(fn ($message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log) => $this->action($message, $dbForConsole, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $log));
}
/**
@@ -110,51 +109,50 @@ class Builds extends Action
* @param Database $dbForProject
* @param GitHub $github
* @param Document $project
* @param Document $resource
* @param Document $function
* @param Document $deployment
* @param Document $template
* @param Log $log
* @return void
* @throws \Utopia\Database\Exception
*
* @throws Exception
*/
protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Database $dbForConsole, Database $dbForProject, GitHub $github, Document $project, Document $resource, Document $deployment, Document $template, Log $log): void
protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Database $dbForConsole, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, Log $log): void
{
$resourceKey = match($resource->getCollection()) {
'functions' => 'functionId',
'sites' => 'siteId',
default => throw new \Exception('Invalid resource type')
};
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
$log->addTag($resourceKey, $resource->getId());
$functionId = $function->getId();
$log->addTag('functionId', $function->getId());
$resource = $dbForProject->getDocument($resource->getCollection(), $resource->getId());
if ($resource->isEmpty()) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new \Exception('Function not found', 404);
}
$log->addTag('deploymentId', $deployment->getId());
$deploymentId = $deployment->getId();
$log->addTag('deploymentId', $deploymentId);
$deployment = $dbForProject->getDocument('deployments', $deployment->getId());
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new \Exception('Deployment not found', 404);
}
// todo: figure out a better way, entrypoint is not required for sites
if ($resource->getCollection() === 'functions' && empty($deployment->getAttribute('entrypoint', ''))) {
if (empty($deployment->getAttribute('entrypoint', ''))) {
throw new \Exception('Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".', 500);
}
$version = $this->getVersion($resource);
$runtime = $this->getRuntime($resource, $version);
$spec = Config::getParam('runtime-specifications')[$resource->getAttribute('specifications', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$version = $function->getAttribute('version', 'v2');
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specifications', APP_FUNCTION_SPECIFICATION_DEFAULT)];
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$key = $function->getAttribute('runtime');
$runtime = $runtimes[$key] ?? null;
if (\is_null($runtime)) {
throw new \Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
// Realtime preparation
$allEvents = Event::generateEvents("{$resource->getCollection()}.[{$resourceKey}].deployments.[deploymentId].update", [
$resourceKey => $resource->getId(),
$allEvents = Event::generateEvents('functions.[functionId].deployments.[deploymentId].update', [
'functionId' => $function->getId(),
'deploymentId' => $deployment->getId()
]);
@@ -173,10 +171,10 @@ class Builds extends Action
'deploymentId' => $deployment->getId(),
'status' => 'processing',
'path' => '',
'runtime' => $resource->getAttribute('runtime'),
'runtime' => $function->getAttribute('runtime'),
'source' => $deployment->getAttribute('path', ''),
'sourceType' => strtolower($deviceForFunctions->getType()),
'logs' => [],
'logs' => '',
'endTime' => null,
'duration' => 0,
'size' => 0
@@ -265,7 +263,7 @@ class Builds extends Action
} elseif ($isNewBuild && $isVcsEnabled) {
// VCS and VCS+Temaplte
$tmpDirectory = '/tmp/builds/' . $buildId . '/code';
$rootDirectory = $resource->getAttribute('providerRootDirectory', '');
$rootDirectory = $function->getAttribute('providerRootDirectory', '');
$rootDirectory = \rtrim($rootDirectory, '/');
$rootDirectory = \ltrim($rootDirectory, '.');
$rootDirectory = \ltrim($rootDirectory, '/');
@@ -346,7 +344,7 @@ class Builds extends Action
Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Commit and push
$exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
$exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($function->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to push code repository: ' . $stderr);
@@ -364,7 +362,7 @@ class Builds extends Action
$deployment->setAttribute('providerCommitHash', $providerCommitHash ?? '');
$deployment->setAttribute('providerCommitAuthorUrl', $authorUrl);
$deployment->setAttribute('providerCommitAuthor', 'Appwrite');
$deployment->setAttribute('providerCommitMessage', "Create '" . $resource->getAttribute('name', '') . "' function");
$deployment->setAttribute('providerCommitMessage', "Create '" . $function->getAttribute('name', '') . "' function");
$deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash");
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
@@ -395,10 +393,9 @@ class Builds extends Action
}
$directorySize = $localDevice->getDirectorySize($tmpDirectory);
$sizeLimit = (int)System::getEnv('_APP_COMPUTE_SIZE_LIMIT', '30000000');
if ($directorySize > $sizeLimit) {
throw new \Exception('Repository directory size should be less than ' . number_format($sizeLimit / 1048576, 2) . ' MBs.');
$functionsSizeLimit = (int)System::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', '30000000');
if ($directorySize > $functionsSizeLimit) {
throw new \Exception('Repository directory size should be less than ' . number_format($functionsSizeLimit / 1048576, 2) . ' MBs.');
}
$tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory);
@@ -418,7 +415,7 @@ class Builds extends Action
$directorySize = $deviceForFunctions->getFileSize($source);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize));
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForConsole);
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole);
}
/** Request the executor to build the code... */
@@ -426,7 +423,7 @@ class Builds extends Action
$build = $dbForProject->updateDocument('builds', $buildId, $build);
if ($isVcsEnabled) {
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForConsole);
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole);
}
/** Trigger Webhook */
@@ -436,8 +433,8 @@ class Builds extends Action
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project)
->setEvent("{$resource->getCollection()}.[{$resourceKey}].deployments.[deploymentId].update")
->setParam($resourceKey, $resource->getId())
->setEvent('functions.[functionId].deployments.[deploymentId].update')
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId())
->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules())));
@@ -467,26 +464,23 @@ class Builds extends Action
$vars = [];
// Shared vars
foreach ($resource->getAttribute('varsProject', []) as $var) {
foreach ($function->getAttribute('varsProject', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
// Function vars
foreach ($resource->getAttribute('vars', []) as $var) {
foreach ($function->getAttribute('vars', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$cpus = $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT;
$memory = max($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT, 1024); // We have a minimum of 1024MB here because some runtimes can't compile with less memory than this.
$timeout = (int) System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT', 900);
$cpus = $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT;
$memory = max($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT, 1024); // We have a minimum of 1024MB here because some runtimes can't compile with less memory than this.
$jwtExpiry = (int)System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT', 900);
$jwtExpiry = (int)System::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
$apiKey = $jwtObj->encode([
'projectId' => $project->getId(),
'scopes' => $resource->getAttribute('scopes', [])
'scopes' => $function->getAttribute('scopes', [])
]);
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
@@ -495,11 +489,19 @@ class Builds extends Action
// Appwrite vars
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey,
'APPWRITE_FUNCTION_ID' => $function->getId(),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_FUNCTION_CPUS' => $cpus,
'APPWRITE_FUNCTION_MEMORY' => $memory,
'APPWRITE_VERSION' => APP_VERSION_STABLE,
'APPWRITE_REGION' => $project->getAttribute('region'),
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
'APPWRITE_COMPUTE_CPUS' => $cpus,
'APPWRITE_COMPUTE_MEMORY' => $memory,
'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''),
'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''),
'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''),
@@ -514,39 +516,7 @@ class Builds extends Action
'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''),
]);
switch ($resource->getCollection()) {
case 'functions':
$vars = [
...$vars,
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey,
'APPWRITE_FUNCTION_ID' => $resource->getId(),
'APPWRITE_FUNCTION_NAME' => $resource->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
];
break;
case 'sites':
$vars = [
...$vars,
'APPWRITE_SITE_API_ENDPOINT' => $endpoint,
'APPWRITE_SITE_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey,
'APPWRITE_SITE_ID' => $resource->getId(),
'APPWRITE_SITE_NAME' => $resource->getAttribute('name'),
'APPWRITE_SITE_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_SITE_PROJECT_ID' => $project->getId(),
'APPWRITE_SITE_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_SITE_RUNTIME_VERSION' => $runtime['version'] ?? '',
];
break;
}
$command = $this->getCommand(
resource: $resource,
deployment: $deployment
);
$command = $deployment->getAttribute('commands', '');
$response = null;
$err = null;
@@ -559,11 +529,11 @@ class Builds extends Action
$isCanceled = false;
Co::join([
Co\go(function () use ($executor, &$response, $project, $deployment, $source, $resource, $runtime, $vars, $command, $cpus, $memory, $timeout, &$err, $version) {
Co\go(function () use ($executor, &$response, $project, $deployment, $source, $function, $runtime, $vars, $command, $cpus, $memory, &$err) {
try {
$version = $function->getAttribute('version', 'v2');
$command = $version === 'v2' ? 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh' : 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh "' . \trim(\escapeshellarg($command), "\'") . '"';
// TODO: Detect adapter if adapter is empty
$response = $executor->createRuntime(
deploymentId: $deployment->getId(),
projectId: $project->getId(),
@@ -572,24 +542,21 @@ class Builds extends Action
version: $version,
cpus: $cpus,
memory: $memory,
timeout: $timeout,
remove: true,
entrypoint: $deployment->getAttribute('entrypoint', 'package.json'), // TODO: change this later so that sites don't need to have an entrypoint
entrypoint: $deployment->getAttribute('entrypoint'),
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
variables: $vars,
command: $command,
outputDirectory: $resource->getAttribute('outputDirectory', '')
command: $command
);
} catch (\Throwable $error) {
$err = $error;
}
}),
Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, $timeout, &$err, &$isCanceled) {
Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, &$err, &$isCanceled) {
try {
$executor->getLogs(
deploymentId: $deployment->getId(),
projectId: $project->getId(),
timeout: $timeout,
callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $allEvents, $project, &$isCanceled) {
if ($isCanceled) {
return;
@@ -609,29 +576,9 @@ class Builds extends Action
return;
}
// Get only valid UTF8 part - removes leftover half-multibytes causing SQL errors
$logs = \mb_substr($logs, 0, null, 'UTF-8');
$logs = \mb_substr($logs, 0, null, 'UTF-8'); // Get only valid UTF8 part - removes leftover half-multibytes causing SQL errors
$currentChunks = $build->getAttribute('logs', []);
// Parse styled&timestamped logs
$streamChunks = [];
$streamLogs = \str_replace("\\n", "{APPWRITE_LINEBREAK_PLACEHOLDER}", $logs);
foreach (\explode("\n", $streamLogs) as $streamLog) {
if (empty($streamLog)) {
continue;
}
$streamLog = \str_replace("{APPWRITE_LINEBREAK_PLACEHOLDER}", "\n", $streamLog);
$streamParts = \explode(" ", $streamLog, 2);
$currentChunks[] = [
'timestamp' => $streamParts[0] ?? '',
'content' => $streamParts[1] ?? ''
];
}
$build = $build->setAttribute('logs', $currentChunks);
$build = $build->setAttribute('logs', $build->getAttribute('logs', '') . $logs);
$build = $dbForProject->updateDocument('builds', $build->getId(), $build);
/**
@@ -673,7 +620,7 @@ class Builds extends Action
$endTime = DateTime::now();
$durationEnd = \microtime(true);
$buildSizeLimit = (int)System::getEnv('_APP_COMPUTE_BUILD_SIZE_LIMIT', '2000000000');
$buildSizeLimit = (int)System::getEnv('_APP_FUNCTIONS_BUILD_SIZE_LIMIT', '2000000000');
if ($response['size'] > $buildSizeLimit) {
throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.');
}
@@ -685,30 +632,22 @@ class Builds extends Action
$build->setAttribute('status', 'ready');
$build->setAttribute('path', $response['path']);
$build->setAttribute('size', $response['size']);
// $build->setAttribute('logs', $response['output']); // TODO: Figure out how to write them all at the end
$build->setAttribute('logs', $response['output']);
$build = $dbForProject->updateDocument('builds', $buildId, $build);
if ($isVcsEnabled) {
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForConsole);
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole);
}
Console::success("Build id: $buildId created");
/** Set auto deploy */
if ($deployment->getAttribute('activate') === true) {
$resource->setAttribute('deploymentInternalId', $deployment->getInternalId());
$resource->setAttribute('live', true);
switch ($resource->getCollection()) {
case 'functions':
$resource->setAttribute('deployment', $deployment->getId());
$resource = $dbForProject->updateDocument('functions', $resource->getId(), $resource);
break;
case 'sites':
$resource->setAttribute('deploymentId', $deployment->getId());
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
break;
}
$function->setAttribute('deploymentInternalId', $deployment->getInternalId());
$function->setAttribute('deployment', $deployment->getId());
$function->setAttribute('live', true);
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
}
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
@@ -719,14 +658,12 @@ class Builds extends Action
/** Update function schedule */
// Inform scheduler if function is still active
if ($resource->getCollection() === 'functions') {
$schedule = $dbForConsole->getDocument('schedules', $resource->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $resource->getAttribute('schedule'))
->setAttribute('active', !empty($resource->getAttribute('schedule')) && !empty($resource->getAttribute('deployment')));
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
}
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
} catch (\Throwable $th) {
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
@@ -738,19 +675,12 @@ class Builds extends Action
$build->setAttribute('endTime', $endTime);
$build->setAttribute('duration', \intval(\ceil($durationEnd - $durationStart)));
$build->setAttribute('status', 'failed');
$datetime = new \DateTime();
$build->setAttribute('logs', [
[
'timestamp' => $datetime->format('Y-m-d\TH:i:s.vP'),
'content' => "[31m" . $th->getMessage() . "[0m"
]
]);
$build->setAttribute('logs', $th->getMessage());
$build = $dbForProject->updateDocument('builds', $buildId, $build);
if ($isVcsEnabled) {
$this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForConsole);
$this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole);
}
} finally {
/**
@@ -769,126 +699,34 @@ class Builds extends Action
channels: $target['channels'],
roles: $target['roles']
);
$this->sendUsage(
resource:$resource,
build: $build,
project: $project,
queue: $queueForUsage
);
}
}
protected function sendUsage(Document $resource, Document $build, Document $project, Usage $queue): void
{
$key = match($resource->getCollection()) {
'functions' => 'functionInternalId',
'sites' => 'siteInternalId',
default => throw new \Exception('Invalid resource type')
};
$metrics = match($resource->getCollection()) {
'functions' => [
'builds' => METRIC_FUNCTION_ID_BUILDS,
'buildsSuccess' => METRIC_FUNCTION_ID_BUILDS_SUCCESS,
'buildsFailed' => METRIC_FUNCTION_ID_BUILDS_FAILED,
'buildsComputeSuccess' => METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS,
'buildsComputeFailed' => METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED,
'buildsStorage' => METRIC_FUNCTION_ID_BUILDS_STORAGE,
'buildsCompute' => METRIC_FUNCTION_ID_BUILDS_COMPUTE,
'buildsMbSeconds' => METRIC_FUNCTION_ID_BUILDS_MB_SECONDS
],
'sites' => [
'builds' => METRIC_SITES_ID_BUILDS,
'buildsSuccess' => METRIC_SITES_ID_BUILDS_SUCCESS,
'buildsFailed' => METRIC_SITES_ID_BUILDS_FAILED,
'buildsComputeSuccess' => METRIC_SITES_ID_BUILDS_COMPUTE_SUCCESS,
'buildsComputeFailed' => METRIC_SITES_ID_BUILDS_COMPUTE_FAILED,
'buildsStorage' => METRIC_SITES_ID_BUILDS_STORAGE,
'buildsCompute' => METRIC_SITES_ID_BUILDS_COMPUTE,
'buildsMbSeconds' => METRIC_SITES_ID_BUILDS_MB_SECONDS
]
};
switch ($build->getAttribute('status')) {
case 'ready':
$queue
/** Trigger usage queue */
if ($build->getAttribute('status') === 'ready') {
$queueForUsage
->addMetric(METRIC_BUILDS_SUCCESS, 1) // per project
->addMetric(METRIC_BUILDS_COMPUTE_SUCCESS, (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsSuccess']), 1) // per function
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsComputeSuccess']), (int)$build->getAttribute('duration', 0) * 1000);
break;
case 'failed':
$queue
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_SUCCESS), 1) // per function
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS), (int)$build->getAttribute('duration', 0) * 1000);
} elseif ($build->getAttribute('status') === 'failed') {
$queueForUsage
->addMetric(METRIC_BUILDS_FAILED, 1) // per project
->addMetric(METRIC_BUILDS_COMPUTE_FAILED, (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsFailed']), 1) // per function
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsComputeFailed']), (int)$build->getAttribute('duration', 0) * 1000);
break;
}
$queue
->addMetric(METRIC_BUILDS, 1) // per project
->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0))
->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(METRIC_BUILDS_MB_SECONDS, (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['builds']), 1) // per function
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsStorage']), $build->getAttribute('size', 0))
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsCompute']), (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsMbSeconds']), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->setProject($project)
->trigger();
}
protected function getRuntime(Document $resource, string $version): array
{
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$key = $resource->getAttribute('runtime');
$runtime = match ($resource->getCollection()) {
'functions' => $runtimes[$resource->getAttribute('runtime')] ?? null,
'sites' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
default => null
};
if (\is_null($runtime)) {
throw new \Exception('Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
}
return $runtime;
}
protected function getVersion(Document $resource): string
{
return match ($resource->getCollection()) {
'functions' => $resource->getAttribute('version', 'v2'),
'sites' => 'v4',
};
}
protected function getCommand(Document $resource, Document $deployment): string
{
if ($resource->getCollection() === 'functions') {
return $deployment->getAttribute('commands', '');
} elseif ($resource->getCollection() === 'sites') {
$commands = [];
$commands[] = $deployment->getAttribute('installCommand', '');
$commands[] = $deployment->getAttribute('buildCommand', '');
$frameworks = Config::getParam('frameworks', []);
$framework = $frameworks[$resource->getAttribute('framework', '')] ?? null;
if (!is_null($framework)) {
$adapter = ($framework['adapters'] ?? [])[$resource->getAttribute('adapter', '')] ?? null;
if (!is_null($adapter) && isset($adapter['bundleCommand'])) {
$commands[] = $adapter['bundleCommand'];
}
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_FAILED), 1) // per function
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED), (int)$build->getAttribute('duration', 0) * 1000);
}
$commands = array_filter($commands, fn ($command) => !empty($command));
return implode(' && ', $commands);
$queueForUsage
->addMetric(METRIC_BUILDS, 1) // per project
->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0))
->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(METRIC_BUILDS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS), 1) // per function
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $build->getAttribute('size', 0))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$build->getAttribute('duration', 0) * 1000)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->setProject($project)
->trigger();
}
return '';
}
/**
@@ -898,7 +736,7 @@ class Builds extends Action
* @param string $owner
* @param string $repositoryName
* @param Document $project
* @param Document $resource
* @param Document $function
* @param string $deploymentId
* @param Database $dbForProject
* @param Database $dbForConsole
@@ -909,9 +747,9 @@ class Builds extends Action
* @throws Conflict
* @throws Restricted
*/
protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $resource, string $deploymentId, Database $dbForProject, Database $dbForConsole): void
protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $function, string $deploymentId, Database $dbForProject, Database $dbForConsole): void
{
if ($resource->getAttribute('providerSilentMode', false) === true) {
if ($function->getAttribute('providerSilentMode', false) === true) {
return;
}
@@ -933,14 +771,16 @@ class Builds extends Action
default => $status
};
$resourceName = $resource->getAttribute('name');
$functionName = $function->getAttribute('name');
$projectName = $project->getAttribute('name');
$name = "{$resourceName} ({$projectName})";
$name = "{$functionName} ({$projectName})";
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$providerTargetUrl = "{$protocol}://{$hostname}/console/project-{$project->getId()}/functions/function-{$resource->getId()}";
$functionId = $function->getId();
$projectId = $project->getId();
$providerTargetUrl = $protocol . '://' . $hostname . "/console/project-$projectId/functions/function-$functionId";
$github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, $state, $message, $providerTargetUrl, $name);
}
@@ -967,34 +807,9 @@ class Builds extends Action
// Wrap in try/finally to ensure lock file gets deleted
try {
$resourceType = match($resource->getCollection()) {
'functions' => 'function',
'sites' => 'site',
default => throw new \Exception('Invalid resource type')
};
$rule = Authorization::skip(fn () => $dbForConsole->findOne('rules', [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("resourceType", ["deployment"]),
Query::equal("resourceInternalId", [$deployment->getInternalId()])
]));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$previewUrl = match($resource->getCollection()) {
'functions' => '',
'sites' => !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '',
default => throw new \Exception('Invalid resource type')
};
$previweQrCode = match($resource->getCollection()) {
'functions' => '',
'sites' => 'https://cloud.appwrite.io/v1/avatars/qr?text=' . $previewUrl,
default => throw new \Exception('Invalid resource type')
};
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
$comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl, $previweQrCode);
$comment->addBuild($project, $function, $status, $deployment->getId(), ['type' => 'logs']);
$github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
} finally {
$dbForConsole->deleteDocument('vcsCommentLocks', $commentId);
+51 -9
View File
@@ -22,6 +22,7 @@ use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Domains\Domain;
use Utopia\Fetch\Client;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
@@ -43,16 +44,18 @@ class Certificates extends Action
$this
->desc('Certificates worker')
->inject('message')
->inject('project')
->inject('dbForConsole')
->inject('queueForMails')
->inject('queueForEvents')
->inject('queueForFunctions')
->inject('log')
->callback(fn (Message $message, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log) => $this->action($message, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $log));
->callback(fn (Message $message, Document $project, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log) => $this->action($message, $project, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $log));
}
/**
* @param Message $message
* @param Document $project
* @param Database $dbForConsole
* @param Mail $queueForMails
* @param Event $queueForEvents
@@ -62,7 +65,7 @@ class Certificates extends Action
* @throws Throwable
* @throws \Utopia\Database\Exception
*/
public function action(Message $message, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log): void
public function action(Message $message, Document $project, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log): void
{
$payload = $message->getPayload() ?? [];
@@ -76,7 +79,7 @@ class Certificates extends Action
$log->addTag('domain', $domain->get());
$this->execute($domain, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $log, $skipRenewCheck);
$this->execute($domain, $project, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $log, $skipRenewCheck);
}
/**
@@ -90,7 +93,7 @@ class Certificates extends Action
* @throws Throwable
* @throws \Utopia\Database\Exception
*/
private function execute(Domain $domain, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, bool $skipRenewCheck = false): void
private function execute(Domain $domain, Document $project, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, bool $skipRenewCheck = false): void
{
/**
* 1. Read arguments and validate domain
@@ -155,16 +158,26 @@ class Certificates extends Action
// Prepare folder name for certbot. Using this helps prevent miss-match in LetsEncrypt configuration when renewing certificate
$folder = ID::unique();
// Generate certificate files using Let's Encrypt
$letsEncryptData = $this->issueCertificate($folder, $domain->get(), $email);
try {
// Generate certificate files using Let's Encrypt
$letsEncryptData = $this->issueCertificate($folder, $domain->get(), $email);
// Give certificates to Traefik
$this->applyCertificateFiles($folder, $domain->get(), $letsEncryptData);
} catch (\Throwable $th) {
Console::error('Failed to generate Lets Encrypt certificate');
}
// Command succeeded, store all data into document
$logs = 'Certificate successfully generated.';
$certificate->setAttribute('logs', \mb_strcut($logs, 0, 1000000));// Limit to 1MB
// Give certificates to Traefik
$this->applyCertificateFiles($folder, $domain->get(), $letsEncryptData);
try {
// TEMP: add custom hostnames to cloudflare
$this->addCustomHostnameToRegistrar($project, $domain->get());
} catch (\Throwable $th) {
Console::error('Failed to add custom hostname to registrar: ' . $th->getMessage());
}
// Update certificate info stored in database
$certificate->setAttribute('renewDate', $this->getRenewDate($domain->get()));
@@ -197,6 +210,35 @@ class Certificates extends Action
}
}
/**
* Add custom hostname to Cloudflare registrar
*
* @param Document $project
* @param string $hostname
* @return void
* @throws Exception
*/
private function addCustomHostnameToRegistrar(Document $project, string $hostname): void
{
$client = new Client();
$client
->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON)
->addHeader('Authorization', 'Bearer ' . System::getEnv('_APP_SYSTEM_CLOUDFLARE_TOKEN'));
$response = $client->fetch("https://api.cloudflare.com/client/v4/zones/b2d0e62383d3c0f6299efab107af2c7a/custom_hostnames", Client::METHOD_POST, [
'hostname' => $hostname,
'ssl' => [
"method" => "http",
"type" => "dv",
"wildcard" => false
]
]);
if ($response->getStatusCode() !== 201) {
throw new Exception('Failed to add custom hostname to Cloudflare: ' . $response->getBody());
}
}
/**
* Save certificate data into database.
*
+34 -138
View File
@@ -47,7 +47,6 @@ class Deletes extends Action
->inject('dbForConsole')
->inject('getProjectDB')
->inject('deviceForFiles')
->inject('deviceForSites')
->inject('deviceForFunctions')
->inject('deviceForBuilds')
->inject('deviceForCache')
@@ -55,14 +54,14 @@ class Deletes extends Action
->inject('executionRetention')
->inject('auditRetention')
->inject('log')
->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log) => $this->action($message, $dbForConsole, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $abuseRetention, $executionRetention, $auditRetention, $log));
->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log) => $this->action($message, $dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $abuseRetention, $executionRetention, $auditRetention, $log));
}
/**
* @throws Exception
* @throws Throwable
*/
public function action(Message $message, Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log): void
public function action(Message $message, Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log): void
{
$payload = $message->getPayload() ?? [];
@@ -85,16 +84,13 @@ class Deletes extends Action
case DELETE_TYPE_DOCUMENT:
switch ($document->getCollection()) {
case DELETE_TYPE_PROJECTS:
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $document);
break;
case DELETE_TYPE_SITES:
$this->deleteSite($dbForConsole, $getProjectDB, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $document, $project);
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $document);
break;
case DELETE_TYPE_FUNCTIONS:
$this->deleteFunction($dbForConsole, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project);
break;
case DELETE_TYPE_DEPLOYMENTS:
$this->deleteDeployment($dbForConsole, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project);
$this->deleteDeployment($getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project);
break;
case DELETE_TYPE_USERS:
$this->deleteUser($getProjectDB, $document, $project);
@@ -455,12 +451,11 @@ class Deletes extends Action
foreach ($projects as $project) {
$deviceForFiles = getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
$deviceForSites = getDevice(APP_STORAGE_SITES . '/app-' . $project->getId());
$deviceForFunctions = getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
$deviceForBuilds = getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
$deviceForCache = getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $project);
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $project);
$dbForConsole->deleteDocument('projects', $project->getId());
}
}
@@ -478,7 +473,7 @@ class Deletes extends Action
* @throws Authorization
* @throws DatabaseException
*/
private function deleteProject(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
private function deleteProject(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
{
$projectInternalId = $document->getInternalId();
$projectId = $document->getId();
@@ -500,29 +495,30 @@ class Deletes extends Action
$limit = \count($projectCollectionIds) + 25;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
$sharedTables = $sharedTablesV1 || $sharedTablesV2;
while (true) {
$collections = $dbForProject->listCollections($limit);
foreach ($collections as $collection) {
if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '') || !\in_array($collection->getId(), $projectCollectionIds)) {
try {
try {
if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) {
$dbForProject->deleteCollection($collection->getId());
} catch (Throwable $e) {
Console::error('Error deleting ' . $collection->getId() . ' ' . $e->getMessage());
/**
* Ignore junction tables;
*/
if (!preg_match('/^_\d+_\d+$/', $collection->getId())) {
throw $e;
}
} else {
$this->deleteByGroup($collection->getId(), [], database: $dbForProject);
}
} else {
$this->deleteByGroup($collection->getId(), [], database: $dbForProject);
} catch (Throwable $e) {
Console::error('Error deleting '.$collection->getId().' '.$e->getMessage());
}
}
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
if ($sharedTables) {
$collectionsIds = \array_map(fn ($collection) => $collection->getId(), $collections);
if (empty(\array_diff($collectionsIds, $projectCollectionIds))) {
@@ -576,15 +572,21 @@ class Deletes extends Action
], $dbForConsole);
// Delete metadata table
if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$dbForProject->deleteCollection('_metadata');
} else {
$this->deleteByGroup('_metadata', [], $dbForProject);
if ($projectTables) {
$dbForProject->deleteCollection(Database::METADATA);
} elseif ($sharedTablesV1) {
$this->deleteByGroup(Database::METADATA, [], $dbForProject);
} elseif ($sharedTablesV2) {
$queries = \array_map(
fn ($id) => Query::notEqual('$id', $id),
$projectCollectionIds
);
$this->deleteByGroup(Database::METADATA, $queries, $dbForProject);
}
// Delete all storage directories
$deviceForFiles->delete($deviceForFiles->getRoot(), true);
$deviceForSites->delete($deviceForSites->getRoot(), true);
$deviceForFunctions->delete($deviceForFunctions->getRoot(), true);
$deviceForBuilds->delete($deviceForBuilds->getRoot(), true);
$deviceForCache->delete($deviceForCache->getRoot(), true);
@@ -735,100 +737,6 @@ class Deletes extends Action
}
}
/**
* @param callable $getProjectDB
* @param Device $deviceForSites
* @param Device $deviceForFunctions
* @param Device $deviceForBuilds
* @param Document $document function document
* @param Document $project
* @return void
* @throws Exception
*/
private function deleteSite(Database $dbForConsole, callable $getProjectDB, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void
{
$dbForProject = $getProjectDB($project);
$siteId = $document->getId();
$siteInternalId = $document->getInternalId();
/**
* Delete rules for site
*/
Console::info("Deleting rules for site " . $siteId);
$this->deleteByGroup('rules', [
Query::equal('resourceType', ['site']),
Query::equal('resourceInternalId', [$siteInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForConsole, function (Document $document) use ($dbForConsole) {
$this->deleteRule($dbForConsole, $document);
});
/**
* Delete Variables
*/
Console::info("Deleting variables for site " . $siteId);
$this->deleteByGroup('variables', [
Query::equal('resourceType', ['site']),
Query::equal('resourceInternalId', [$siteInternalId])
], $dbForProject);
/**
* Delete Deployments
*/
Console::info("Deleting deployments for site " . $siteId);
$deploymentInternalIds = [];
$this->deleteByGroup('deployments', [
Query::equal('resourceInternalId', [$siteInternalId])
], $dbForProject, function (Document $document) use ($deviceForFunctions, &$deploymentInternalIds) {
$deploymentInternalIds[] = $document->getInternalId();
$this->deleteDeploymentFiles($deviceForFunctions, $document);
});
/**
* Delete rules for all deployments of the site
*/
//TODO: If functions also have previews in the future, change the logic here to use unique identifier for sites and functions
foreach ($deploymentInternalIds as $deploymentInternalId) {
Console::info("Deleting rules for site " . $siteId . "'s deployment " . $deploymentInternalId);
$this->deleteByGroup('rules', [
Query::equal('resourceType', ['deployment']),
Query::equal('resourceInternalId', [$deploymentInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForConsole, function (Document $document) use ($dbForConsole) {
$this->deleteRule($dbForConsole, $document);
});
}
/**
* Delete builds
*/
Console::info("Deleting builds for site " . $siteId);
foreach ($deploymentInternalIds as $deploymentInternalId) {
$this->deleteByGroup('builds', [
Query::equal('deploymentInternalId', [$deploymentInternalId])
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
$this->deleteBuildFiles($deviceForBuilds, $document);
});
}
/**
* Delete VCS Repositories and VCS Comments
*/
Console::info("Deleting VCS repositories and comments linked to site " . $siteId);
$this->deleteByGroup('repositories', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('resourceInternalId', [$siteInternalId]),
Query::equal('resourceType', ['site']),
], $dbForConsole, function (Document $document) use ($dbForConsole) {
$providerRepositoryId = $document->getAttribute('providerRepositoryId', '');
$projectInternalId = $document->getAttribute('projectInternalId', '');
$this->deleteByGroup('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('projectInternalId', [$projectInternalId]),
], $dbForConsole);
});
}
/**
* @param callable $getProjectDB
* @param Device $deviceForFunctions
@@ -995,7 +903,7 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
private function deleteDeployment(Database $dbForConsole, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void
private function deleteDeployment(callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void
{
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
@@ -1005,7 +913,7 @@ class Deletes extends Action
/**
* Delete deployment files
*/
$this->deleteDeploymentFiles($deviceForFunctions, $document); //TODO: For sites, this should be deviceForSites
$this->deleteDeploymentFiles($deviceForFunctions, $document);
/**
* Delete builds
@@ -1018,18 +926,6 @@ class Deletes extends Action
$this->deleteBuildFiles($deviceForBuilds, $document);
});
/**
* Delete rules associated with the deployment
*/
Console::info("Deleting rules for deployment " . $deploymentId);
$this->deleteByGroup('rules', [
Query::equal('resourceType', ['deployment']),
Query::equal('resourceInternalId', [$deploymentInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForConsole, function (Document $document) use ($dbForConsole) {
$this->deleteRule($dbForConsole, $document);
});
/**
* Request executor to delete all deployment containers
*/
+11 -13
View File
@@ -256,9 +256,8 @@ class Functions extends Action
$execution = new Document([
'$id' => $executionId,
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'functionInternalId' => $function->getInternalId(),
'functionId' => $function->getId(),
'deploymentInternalId' => '',
'deploymentId' => '',
'trigger' => $trigger,
@@ -327,7 +326,7 @@ class Functions extends Action
$user ??= new Document();
$functionId = $function->getId();
$deploymentId = $function->getAttribute('deployment', '');
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)];
$log->addTag('deploymentId', $deploymentId);
@@ -404,9 +403,8 @@ class Functions extends Action
$execution = new Document([
'$id' => $executionId,
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'functionInternalId' => $function->getInternalId(),
'functionId' => $function->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'trigger' => $trigger,
@@ -481,8 +479,8 @@ class Functions extends Action
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_COMPUTE_CPUS' => ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT),
'APPWRITE_COMPUTE_MEMORY' => ($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT),
'APPWRITE_FUNCTION_CPUS' => ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT),
'APPWRITE_FUNCTION_MEMORY' => ($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT),
'APPWRITE_VERSION' => APP_VERSION_STABLE,
'APPWRITE_REGION' => $project->getAttribute('region'),
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
@@ -520,8 +518,8 @@ class Functions extends Action
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
);
@@ -560,8 +558,8 @@ class Functions extends Action
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000))// per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000))
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
->trigger()
;
}
@@ -1,112 +0,0 @@
<?php
namespace Appwrite\Sites\Validator;
use Utopia\Validator;
class FrameworkSpecification extends Validator
{
private array $plan;
private array $specifications;
private float $maxCpus;
private int $maxMemory;
public function __construct(array $plan, array $specifications, float $maxCpus, int $maxMemory)
{
$this->plan = $plan;
$this->specifications = $specifications;
$this->maxCpus = $maxCpus;
$this->maxMemory = $maxMemory;
}
/**
* Get Allowed Specifications.
*
* Get allowed specifications taking into account the limits set by the environment variables and the plan.
*
* @return array
*/
public function getAllowedSpecifications(): array
{
$allowedSpecifications = [];
foreach ($this->specifications as $size => $values) {
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
if (!empty($this->plan) && array_key_exists('frameworkSpecifications', $this->plan)) {
if (!\in_array($size, $this->plan['frameworkSpecifications'])) {
continue;
}
}
$allowedSpecifications[] = $size;
}
}
return $allowedSpecifications;
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'Specification must be one of: ' . implode(', ', $this->getAllowedSpecifications());
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_string($value)) {
return false;
}
if (!\in_array($value, $this->getAllowedSpecifications())) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
-19
View File
@@ -198,17 +198,6 @@ abstract class Format
break;
}
break;
case 'sites':
switch ($method) {
case 'getUsage':
case 'getSiteUsage':
switch ($param) {
case 'range':
return 'SiteUsageRange';
}
break;
}
break;
case 'messaging':
switch ($method) {
case 'getUsage':
@@ -397,14 +386,6 @@ abstract class Format
return ['Twenty Four Hours', 'Thirty Days', 'Ninety Days'];
}
break;
case 'sites':
switch ($method) {
case 'getUsage':
case 'getSiteUsage':
// Range Enum Keys
return ['Twenty Four Hours', 'Thirty Days', 'Ninety Days'];
}
break;
case 'users':
switch ($method) {
case 'getUsage':
@@ -1,23 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Logs extends Base
{
public const ALLOWED_ATTRIBUTES = [
'responseStatusCode',
'duration',
'requestMethod',
'requestPath',
'deploymentId'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('executions', self::ALLOWED_ATTRIBUTES); //TODO: Update this later
}
}
@@ -1,26 +0,0 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Sites extends Base
{
public const ALLOWED_ATTRIBUTES = [
'name',
'enabled',
'framework',
'deploymentId',
'buildCommand',
'installCommand',
'outputDirectory',
'installationId'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('sites', self::ALLOWED_ATTRIBUTES);
}
}
-29
View File
@@ -44,8 +44,6 @@ 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;
@@ -86,16 +84,13 @@ use Appwrite\Utopia\Response\Model\ProviderRepository;
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\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;
@@ -107,8 +102,6 @@ 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\UsageUsers;
use Appwrite\Utopia\Response\Model\User;
@@ -147,8 +140,6 @@ class Response extends SwooleResponse
public const MODEL_USAGE_STORAGE = 'usageStorage';
public const MODEL_USAGE_FUNCTIONS = 'usageFunctions';
public const MODEL_USAGE_FUNCTION = 'usageFunction';
public const MODEL_USAGE_SITES = 'usageSites';
public const MODEL_USAGE_SITE = 'usageSite';
public const MODEL_USAGE_PROJECT = 'usageProject';
// Database
@@ -253,16 +244,6 @@ class Response extends SwooleResponse
public const MODEL_VCS_CONTENT = 'vcsContent';
public const MODEL_VCS_CONTENT_LIST = 'vcsContentList';
// Sites
public const MODEL_SITE = 'site';
public const MODEL_SITE_LIST = 'siteList';
public const MODEL_FRAMEWORK = 'framework';
public const MODEL_FRAMEWORK_LIST = 'frameworkList';
public const MODEL_FRAMEWORK_ADAPTER = 'frameworkAdapter';
public const MODEL_TEMPLATE_SITE = 'templateSite';
public const MODEL_TEMPLATE_SITE_LIST = 'templateSiteList';
public const MODEL_TEMPLATE_FRAMEWORK = 'templateFramework';
// Functions
public const MODEL_FUNCTION = 'function';
public const MODEL_FUNCTION_LIST = 'functionList';
@@ -370,14 +351,11 @@ class Response extends SwooleResponse
->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET))
->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE))
->setModel(new BaseList('Site Templates List', self::MODEL_TEMPLATE_SITE_LIST, 'templates', self::MODEL_TEMPLATE_SITE))
->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION))
->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION))
->setModel(new BaseList('Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_LIST, 'providerRepositories', self::MODEL_PROVIDER_REPOSITORY))
->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH))
->setModel(new BaseList('Frameworks List', self::MODEL_FRAMEWORK_LIST, 'frameworks', self::MODEL_FRAMEWORK))
->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME))
->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT))
->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION))
@@ -444,9 +422,6 @@ class Response extends SwooleResponse
->setModel(new Bucket())
->setModel(new Team())
->setModel(new Membership())
->setModel(new Site())
->setModel(new TemplateSite())
->setModel(new TemplateFramework())
->setModel(new Func())
->setModel(new TemplateFunction())
->setModel(new TemplateRuntime())
@@ -457,8 +432,6 @@ class Response extends SwooleResponse
->setModel(new VcsContent())
->setModel(new Branch())
->setModel(new Runtime())
->setModel(new Framework())
->setModel(new FrameworkAdapter())
->setModel(new Deployment())
->setModel(new Execution())
->setModel(new Build())
@@ -490,8 +463,6 @@ class Response extends SwooleResponse
->setModel(new UsageBuckets())
->setModel(new UsageFunctions())
->setModel(new UsageFunction())
->setModel(new UsageSites())
->setModel(new UsageSite())
->setModel(new UsageProject())
->setModel(new Headers())
->setModel(new Specification())
@@ -22,7 +22,7 @@ class ConsoleVariables extends Model
'default' => '',
'example' => '30000000',
])
->addRule('_APP_COMPUTE_SIZE_LIMIT', [
->addRule('_APP_FUNCTIONS_SIZE_LIMIT', [
'type' => self::TYPE_INTEGER,
'description' => 'Maximum file size allowed for deployment in bytes.',
'default' => '',
@@ -51,22 +51,7 @@ class ConsoleVariables extends Model
'description' => 'Defines if AI assistant is enabled.',
'default' => false,
'example' => true,
])
->addRule('_APP_DOMAIN_SITES', [
'type' => self::TYPE_STRING,
'description' => 'A domain to use for site URLs.',
'default' => '',
'example' => 'sites.localhost',
])
->addRule(
'_APP_OPTIONS_FORCE_HTTPS',
[
'type' => self::TYPE_STRING,
'description' => 'Defines if HTTPS is enforced for all requests.',
'default' => '',
'example' => 'enabled',
]
);
]);
}
/**
@@ -94,12 +94,6 @@ class Deployment extends Model
'default' => 0,
'example' => 128,
])
->addRule('domain', [
'type' => self::TYPE_STRING,
'description' => 'Preview domain.',
'default' => '',
'example' => 'deploy1-project1.appwrite.site',
])
->addRule('providerRepositoryName', [
'type' => self::TYPE_STRING,
'description' => 'The name of the vcs provider repository',
@@ -37,18 +37,12 @@ class Execution extends Model
'example' => [Role::any()->toString()],
'array' => true,
])
->addRule('resourceId', [
->addRule('functionId', [
'type' => self::TYPE_STRING,
'description' => 'Resource ID.',
'description' => 'Function ID.',
'default' => '',
'example' => '5e5ea6g16897e',
])
->addRule('resourceType', [
'type' => self::TYPE_STRING,
'description' => 'Resource type.',
'default' => '',
'example' => 'sites',
])
->addRule('trigger', [
'type' => self::TYPE_STRING,
'description' => 'The trigger that caused the function to execute. Possible values can be: `http`, `schedule`, or `event`.',
@@ -112,7 +106,7 @@ class Execution extends Model
])
->addRule('duration', [
'type' => self::TYPE_FLOAT,
'description' => 'Resource(function/site) execution duration in seconds.',
'description' => 'Function execution duration in seconds.',
'default' => 0,
'example' => 0.400,
])
@@ -1,67 +0,0 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Framework extends Model
{
public function __construct()
{
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Framework key.',
'default' => '',
'example' => 'sveltekit',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Framework Name.',
'default' => '',
'example' => 'SvelteKit'
])
->addRule('buildRuntime', [
'type' => self::TYPE_STRING,
'description' => 'Default runtime version.',
'default' => '',
'example' => 'node-22',
])
->addRule('runtimes', [
'type' => self::TYPE_STRING,
'description' => 'List of supported runtime versions.',
'default' => '',
'example' => ['static-1', 'node-22'],
'array' => true,
])
->addRule('adapters', [
'type' => Response::MODEL_FRAMEWORK_ADAPTER,
'description' => 'List of supported adapters.',
'default' => '',
'example' => [[ 'key' => 'static', 'buildRuntime' => 'node-22', 'buildCommand' => 'npm run build', 'installCommand' => 'npm install', 'outputDirectory' => './dist' ]],
'array' => true,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Framework';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_FRAMEWORK;
}
}
@@ -1,59 +0,0 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class FrameworkAdapter extends Model
{
public function __construct()
{
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Adapter key.',
'default' => '',
'example' => 'static',
])
->addRule('installCommand', [
'type' => self::TYPE_STRING,
'description' => 'Default command to download dependencies.',
'default' => '',
'example' => 'npm install',
])
->addRule('buildCommand', [
'type' => self::TYPE_STRING,
'description' => 'Default command to build site into output directory.',
'default' => '',
'example' => 'npm run build',
])
->addRule('outputDirectory', [
'type' => self::TYPE_STRING,
'description' => 'Default output directory of build.',
'default' => '',
'example' => './dist',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Framework Adapter';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_FRAMEWORK_ADAPTER;
}
}
+4 -4
View File
@@ -61,7 +61,7 @@ class Func extends Model
])
->addRule('runtime', [
'type' => self::TYPE_STRING,
'description' => 'Function execution and build runtime.',
'description' => 'Function execution runtime.',
'default' => '',
'example' => 'python-3.8',
])
@@ -94,7 +94,7 @@ class Func extends Model
])
->addRule('schedule', [
'type' => self::TYPE_STRING,
'description' => 'Function execution schedult in CRON format.',
'description' => 'Function execution schedule in CRON format.',
'default' => '',
'example' => '5 4 * * *',
])
@@ -155,8 +155,8 @@ class Func extends Model
->addRule('specification', [
'type' => self::TYPE_STRING,
'description' => 'Machine specification for builds and executions.',
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'default' => APP_FUNCTION_SPECIFICATION_DEFAULT,
'example' => APP_FUNCTION_SPECIFICATION_DEFAULT,
])
;
}
-168
View File
@@ -1,168 +0,0 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Site extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Site ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Site creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Site update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Site name.',
'default' => '',
'example' => 'My Site',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Site enabled.',
'default' => true,
'example' => false,
])
->addRule('live', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is the site deployed with the latest configuration? This is set to false if you\'ve changed an environment variables, entrypoint, commands, or other settings that needs redeploy to be applied. When the value is false, redeploy the site to update it with the latest configuration.',
'default' => true,
'example' => false,
])
->addRule('framework', [
'type' => self::TYPE_STRING,
'description' => 'Site framework.',
'default' => '',
'example' => 'react',
])
->addRule('deploymentId', [
'type' => self::TYPE_STRING,
'description' => 'Site\'s active deployment ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('vars', [
'type' => Response::MODEL_VARIABLE,
'description' => 'Site variables.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('timeout', [
'type' => self::TYPE_INTEGER,
'description' => 'Site request timeout in seconds.',
'default' => 15,
'example' => 300,
])
->addRule('installCommand', [
'type' => self::TYPE_STRING,
'description' => 'The install command used to install the site dependencies.',
'default' => '',
'example' => 'npm install',
])
->addRule('buildCommand', [
'type' => self::TYPE_STRING,
'description' => 'The build command used to build the site.',
'default' => '',
'example' => 'npm run build',
])
->addRule('outputDirectory', [
'type' => self::TYPE_STRING,
'description' => 'The directory where the site build output is located.',
'default' => '',
'example' => 'build',
])
->addRule('installationId', [
'type' => self::TYPE_STRING,
'description' => 'Site VCS (Version Control System) installation id.',
'default' => '',
'example' => '6m40at4ejk5h2u9s1hboo',
])
->addRule('providerRepositoryId', [
'type' => self::TYPE_STRING,
'description' => 'VCS (Version Control System) Repository ID',
'default' => '',
'example' => 'appwrite',
])
->addRule('providerBranch', [
'type' => self::TYPE_STRING,
'description' => 'VCS (Version Control System) branch name',
'default' => '',
'example' => 'main',
])
->addRule('providerRootDirectory', [
'type' => self::TYPE_STRING,
'description' => 'Path to site in VCS (Version Control System) repository',
'default' => '',
'example' => 'sites/helloWorld',
])
->addRule('providerSilentMode', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is VCS (Version Control System) connection is in silent mode? When in silence mode, no comments will be posted on the repository pull or merge requests',
'default' => false,
'example' => false,
])
->addRule('specification', [
'type' => self::TYPE_STRING,
'description' => 'Machine specification for builds and executions.',
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
])
->addRule('buildRuntime', [
'type' => self::TYPE_STRING,
'description' => 'Site build runtime.',
'default' => '',
'example' => 'node-22',
])
->addRule('adapter', [
'type' => self::TYPE_STRING,
'description' => 'Site framework adapter.',
'default' => null,
'example' => 'static',
])
->addRule('fallbackFile', [
'type' => self::TYPE_STRING,
'description' => 'Name of fallback file to use instead of 404 page. If null, Appwrite 404 page will be displayed.',
'default' => null,
'example' => 'index.html',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Site';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_SITE;
}
}
@@ -32,7 +32,7 @@ class Specification extends Model
'type' => self::TYPE_STRING,
'description' => 'Size slug.',
'default' => '',
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT
'example' => APP_FUNCTION_SPECIFICATION_DEFAULT
]);
}
@@ -1,89 +0,0 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class TemplateFramework extends Model
{
public function __construct()
{
$this
->addRule('key', [
'type' => self::TYPE_STRING,
'description' => 'Parent framework key.',
'default' => '',
'example' => 'sveltekit',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Framework Name.',
'default' => '',
'example' => 'SvelteKit'
])
->addRule('installCommand', [
'type' => self::TYPE_STRING,
'description' => 'The install command used to install the dependencies.',
'default' => '',
'example' => 'npm install',
])
->addRule('buildCommand', [
'type' => self::TYPE_STRING,
'description' => 'The build command used to build the deployment.',
'default' => '',
'example' => 'npm run build',
])
->addRule('outputDirectory', [
'type' => self::TYPE_STRING,
'description' => 'The output directory to store the build output.',
'default' => '',
'example' => './build',
])
->addRule('providerRootDirectory', [
'type' => self::TYPE_STRING,
'description' => 'Path to site in VCS (Version Control System) repository',
'default' => '',
'example' => './svelte-kit/starter',
])
->addRule('buildRuntime', [
'type' => self::TYPE_STRING,
'description' => 'Runtime used during build step of template.',
'default' => '',
'example' => 'node-22',
])
->addRule('adapter', [
'type' => self::TYPE_STRING,
'description' => 'Site framework runtime',
'default' => '',
'example' => 'ssr',
])
->addRule('fallbackFile', [
'type' => self::TYPE_STRING,
'description' => 'Fallback file for SPA. Only relevant for static serve runtime.',
'default' => null,
'example' => 'index.html',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Template Framework';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_TEMPLATE_FRAMEWORK;
}
}

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